CS/Software Engineering

[소프트웨어 공학] 7. 아키텍쳐와 패턴 (중요)

grammiboii 2025. 6. 6. 17:33

 

아키텍쳐 기초

수많은 아키텍쳐가 있지만 학부생들 대부분은 원시 코드를 사용한다고 지적해 주셨다.

그 이유에 대해 코딩 테스트를 비판하시며 시스템을 크게 가져가면서 넓게, 크게 봐야 하는데 그걸 테스트 하지 않기 때문이라고 하셨다. 특히 아키텍쳐에 대한 생각 없이 개발하는 사람들이 대체 1순위이다,, 라고 하셨다. 되게 공감되는 이야기이였다. 요즘 채용 프로세스에 과제가 포함되는게 좋은 점인 것 같다.

 

추가로 링크드인을 꼭 가입하라고 하셨다. 나도 링크드인을 통해 modular monoliths를 접했고 현 프로젝트에 접목하고 있는데 정말 이건 혁신이다. 브라운 형님 멋있다

관심있으신 분들은 보시길

https://www.youtube.com/watch?v=5OjqD-ow8GE&t=1s

 

 

레퍼런스가 많이 없어서 고생을 좀 했는데

없으면 내가 해야지!! 레퍼런스로 좋은 예시를 만들어볼 계획이다 ㅎㅎ

 

 

싱글톤 패턴 Singleton Pattern

 

3가지 포인트가 있다

1. 싱글톤 패턴으로 만들어질 객체(데이터베이스 커넥션)는 자기 자신 내부에 정적 멤버 변수로 존재

2. 생성자는 private이어야 한다

3. 또다른 정적 멤버 함수는 객체가 null일때 private 생성자로 객체를 생성하고 있다면 1번에 있는 인스턴스를 반환

 

 

코드상에서 1, 2, 3번

 

 

3번 메서드는 왜 정적 메서드일까? -> 객체가 없으면 멤버함수를 못쓰니깐

 

 

반복자 패턴 Iterator pattern

 

c++ 할 때 standard template library로 많이 사용한 패턴이라고 한다.

 

집합 클래스의 자료구조와 상관없이 집합 소속 요소들에 쉽게 접근할 수 있도록 반복자에게 위임하는 구조.

클라이언트는 데이터 접근 방법을 신경쓰지 않아도 된다.

 

 

 

어댑터 패턴 Adapter pattern

 

사용 가능한 서비스의 인터페이스를 클라이언트가 예상하는 인터페이스에 맞게 조정

여기서 어댑터는

서비스가 제공하는 인터페이스를 클라언트가 기대하는 인터페이스로 조정한다.

 

 

 

예를 들어, 직접 제어하기 어려운 코드 특히 애널리틱 라이브러리가 저체적으로 XML을 JSON으로 변환할 수 있도록 애널러틱 라이브러리를 고침??

 

즉 최대한 코어 어플리케이션이나 권한이 없는 애널리틱 라이브러리를 건들지 않고 중간에 어댑터를 만들어 변환하라는 뜻

 

 

 

 

 

파사드 패턴 Facade Pattern

 

새로운 인터페이스를 만들어 감싸는 점이 어댑터 패턴과 동일하지만

 그 안에 세부 함수들이 존재할때 절차에 신경쓰지 않고 클라이언트가 신경쓰지 않아도 된다는 점이다.

 

즉 그 세부 함수들을 또 하나의 인터페이스로 묶어주는 것이라고 하셨다

 

예를 들어 세탁기를 사용하는데

급수, 탈수, 세재, 헹굼 단계 -> 인터페이스로 존재하는데

 

이 모든걸 합친 인터페이스로 "세탁"이라는 인터페이스를 만드는 것이다.

클라이언트는 세부적인 서비스와 절차를 신경쓸 필요가 없다.

 

프록시 패턴 Proxy Pattern

 

프록시라는게 참 많이 보이는데

"대리"라는 뜻이다.

 

그러니까 아키텍쳐에서 프록시가 나오면 앞에 하나의 서버를 더 두고

로직을 분리해주는 느낌이 많다.

 

교수님의 예시는

캐싱, 접근제한, 로드밸런싱, SSL 등을 처리하는 프록시서버이다.

백엔드의 Tomcat WAS로 생각하면

우리가 만든 서버는 비즈니스 로직만을 처리하고

앞에 놓은 프록시서버는 캐싱, 접근제한, 로드밸런싱을 담당하는 것

 

AWS로 치면 로드밸런싱 제품으로 ELB (ALB) -> 접근제한, SSL 포함

캐싱으로 Cloud Front

요런게 있을 것 같다

 

 

데코레이터 패턴 Decorator Pattern

!!여기 좀 중요한 것 같다!!

 

집합 관계와 위임을 통해 기존 클래스의 동작을 가볍고 유연하게 확장하는 패턴이다.

 

흐름대로 가보면

- 기본적인 코드 수정은 OCP 위배

- 그럼 상속을 사용하자 -> 부모 자식간 컴파일 타임 의존 관계 발생, 마음대로 추가 삭제가 불가능하다

- 이를 해결하기 위해 만들어진게 데코레이터 패턴

 

원리를 알기 위해

먼저 재귀적 합성을 살펴보자

 

 

데코레이터 패턴에는 

- Component (인터페이스)

- Component1 (구현체)

- 데코레이터 (확장 기능이 담김)

 

 

 

여기서 장식을 하는 대상이 누군지를 알아야 하는데

Component 인터페이스인거다.

 

Decorator는 재귀적 합성으로 addBehavior로 추가를 하는거다.

즉 Component 인터페이스의 구현체의 객체를 Decorator로 감싸주는것

 

 

정리를 한번 더 하면 Decorator는 추상적인걸 꾸며준다.

구현체로 만든 객체를 인터페이스로 받아주고

그 객체를 데코레어티로 감싸 추가적인 동작을 실행한다

 

 

코드로 보는게 명확하다

	interface Component {
        void operation();
    }

    class Component1 implements Component {
        public void operation() {
        }
    }

    abstract class Decorator implements Component {
        Component wrappee; // 원본 객체를 composition

        Decorator(Component component) {
            this.wrappee = component;
        }

        public void operation() {
            wrappee.operation(); // 위임
        }
    }

    class Decorator1 extends Decorator {

        Decorator1(Component component) {
            super(component);
        }

        public void operation() {
            super.operation(); // 원본 객체를 상위 클래스의 위임을 통해 실행하고
            extraOperation(); // 장식 클래스만의 메소드를 실행한다.
        }

        void extraOperation() {
        }
    }

    class Decorator2 extends Decorator {

        Decorator2(Component component) {
            super(component);
        }

        public void operation() {
            super.operation();
            extraOperation();
        }

        void extraOperation() {
        }
    }

 

 

실행

        Component obj = new Component1();

        Component deco1 = new Decorator1(obj);
        deco1.operation();

        Component deco2 = new Decorator2(obj);
        deco2.operation();
        
        Component deco3 = new Decorator1(new Decorator2(obj));
        deco3.operation();

 

 

 

예시로 Notifier 알림을 개발할때

 

3가지 데코레이가 존재

Notifier는 interface겠지?

 

이걸 사용한다고 했을때

BaseNotifier를 만들어 주고 -> 기본 동작을 수행하는 녀석 (구현체)

Notifier notifier = new NotifierImpl();

 

SMS를 사용할때는

Notifier smsDecorated = new SmsDecorator(notifier);

smsDecorated.send();

 

FaceBook을 사용할때는

Notifier facebookDecorated = new FaceBookDecorator(notifier);

facebookDecorated.send();

 

SMS + FaceBook 사용할때는

Nottifier smsFacebookDecorated = new FaceBookDecorator( new SmsDecorator(notifier));

smsFacebookDecorated.send()

 

이런식일거다.

 

 

 

<------------->

주의! 밑에 그림과 내가 쓴 코드 네이밍이 다르다

 

내가 NotifierImpl이라고 한 부분이 BaseDecorator

 

 

 

 

하지만 주의해야할점

합성되는 순서에 따라 로직이 결정된다

-> 그러므로 순서가 중요하고, 순서 상관없이 수행하고 싶다면 데코레이터 패턴을 사용하면 안된다

 

Nottifier smsFacebookDecorated = new FaceBookDecorator( new SmsDecorator(notifier));

 

내부 구현은

super();

operate();

메서드에서 이 순서로 구현하고 send를 호출한다면

 

Base operation -> SMS operation -> Facebook operation 순서대로 수행 될 것

 

 

만약 이 순서를 원하지 않는다면 뒤에 나오는

팩토리 메서드 패턴 혹은 추상 팩터리 패턴을 사용하면 된다.

 

 

팩토리 메서드 패턴 Factory Method Pattern

 

클래스의 새로운 객체를 생성할때 사용된다.

객체를 생성하는 책임을 분리하는 패턴이다.

 

 

 

 

 

Dialog가 Button을 만들어내는 클래스인 상황에서 (팩토리)

다이얼로그 형태가 다를때마다 버튼, 렌더링 방식은 다를거다.

 

그렇기 때문에 여기서 사용하는 생성자를 분리하라는 뜻이다.

즉 Dialog가 사용하는 생성자를 다른 클래스로 분리

 

 

추상 팩토리 패턴 Abstract Factory Pattern

 

단일책임 원칙

SRP를 지키기 위해 추상 인터페이스로 객체 패밀리를 생성하는 것이다.

 

 

 

객체 패밀리를 만드는 점이

위에서 본 어댑터 ->  파사드 넘어갈때랑 굉장히 비슷한 것 같다.

 

 

 

 

 

 

예시 종합본

 

 

new라고 적어놓은 부분은

GUIFactory factory = new WinFactory();

GUIFactory factory = new MacFactory();

이렇게 사용한다는 뜻

 

상태 패턴 State Pattern

 

상태에 맞게 객체 동작을 변경해야 하는 경우에 사용한다.

 

Context가 가지고 있는 State 멤버 변수 구현체로 어떤걸 집어넣어 주는지에 따라

어떻게 동작을 하는지가 변경된다

 

 

교수님이 말하신 내용은 아니지만 개인적으로 이 패턴은 

추상적인것에 의존해야한다인 -> DIP를 적용해야 하는 구체적인 상황에 대해 말한다고 느껴진다.

 

 

전략 패턴 Strategy Pattern

 

상태 패턴과 비슷하다

 

어떤 전략을 가지고 있는지에 따라 수행할 오퍼레이션을 달리한다.

 

차이점은?

상태 패턴은 스테이트 머신끼리 의존관계가 존재한다.

예를 들어, Draft -> Moderation -> Published 상태는 전 단계가 끝나야 진행되는 것이기 때문에

일종의 의존 관계가 존재한다

 

하지만 전략 패턴은 그냥 나열이다.

Road, PublicTransport, Walking 전략은 의존관계가 전혀 없다.

걸어가든, 지하철을 타든, 운전을 하든 그냥 그 전략에만 집중하면 된다.

 

 

 

단점으로는

- 전략이 소수일때는 과한 구조일 수 있다

 

 

관찰자 패턴 Observer Pattern

옵서버는 변경을 통지받고 접근을 요청하는 클래스이다.

구체적으로 데이터를 보관하고 있는 Subject가 그 데이터를 이용하는 옵서버와 효과적으로 통신하며 느슨하게 결합된 패턴이다.

 

즉 Subject - 옵서버 목록을 유지, 변경을 고지

Observer - 변경을 통지받고 접근을 요청

 

이벤트기반 통신 / 메시징 큐에서

publisher, subscriber랑 똑같은 것 같다

 

 

밑에 그림에서 Editor가 publisher

event listener들이 subscriber가 되겠다

 

 

 


역시 내가 좋아하는 교수님.,,

바로 publisher, subsriber에 대해 이야기를 해주셨다

 

채팅앱을 예시로 들어주셨는데

xmpp를 사용하는 오픈소스로 Jabber를 소개해 주셨다. 요건 생략

 

 

Composite Pattern

특정 클래스가 어떤 객체의 집합을 가지고 있는 패턴이다.

 

예를 들어

GUI 컴포넌트가 여러 위젯을 가지고 있으면 여러 컴포넌트를 가지고 있는다고 하는데 이는 컨테이너라고도 부른다.

 

 

Memento Pattern

컴포넌트의 여러 상태를 저장하고 복원할 수 있는 패턴.

 

 

 

AOP

aspect oriented programming

내가 좋아하는 관점 지향이 수업 중간에 잠깐 나왔다

 

기존 OOP에서 여러 클래스에 넘나드는 공통적인 기능을 활성화 할떄 aop를 사용한다.

세오스 스터디를 진행하며 이걸 구체적으로 정리한 적이 있는데 깃허브 리드미에만 올려서... 나중에 블로그에도 옮겨야겠다

 

AOP를 사용하는 대표적인 예시로 로깅이 있다.

 

 

아키텍쳐 평가

아키텍쳐나 디자인 패턴의 속성, 강점 및 약점을 결정하는 방법이다.

 

SAAM

software architecture analysis method

시나리오 기반 평가 방법

여러가지 시나리오를 가지고 실행이 되는지 확인하는것

즉 테스트케이스를 여러가지 경우로 실행해보는것

 

ATAM

architecture trade-off analysis method

여러가지 품질 속성에 초점을 맞춰 평가하는 방법

그후 업무 배경 및 요구 사항을 본 다음에 품질 속성을 뽑아낸다

이에 맞춰서 시나리오를 작성하고 그에 따른 아키텍쳐를 만들어 접근법을 본 후 의사결정을 내린다.

 

즉, 반복적으로 아키텍쳐를 적용해봄 -> 아키텍쳐간의 trade off를 비교해봄

 

SAAM과 다른 점으로 품질속성이 추가되어 있다는 점이 있다.

 

 

정리

 

배운 디자인 패턴을 정리하면

- 싱글톤 패턴  Singleton

    인스턴스를 하나만

- 반복자 패턴  Iterator

    인터페이스로 순회

- 어댑터 패턴  Adaptor

     interface로 감싸기, 사용자에게 친숙한 인터페이스를 제공

- 파사드 패턴  Facade

    어댑터에서 순서를 묶어서 제공 EX) 세탁기

- 프록시 패턴  Proxy

    앞에 대행자를 추가 EX) SSL, 로드밸런싱

- 팩토리 메서드 패턴  Factory method

    생성자를 분리

- 추상 팩토리 패턴  Abstract Factory

    interface로 객체 패밀리를 생성

- 상태 패턴  State

    경우의수를 interface로 관리

- 전략 패턴  Strategy

    스테이트랑 유사한데 전략끼리의 의존성, 연관관계가 없음 

- 관찰자 Observer

    Publisher Subscriber

- Composite Pattern

    집합을 가지고 있음

- Memento Pattern

    상태를 기억

 

평가 방법

- SAAM

- ATAM

    SAAM과 다른점은 품질 속성이 추가됨