상세 컨텐츠

본문 제목

디자인 패턴에 대한 정리(1)

카테고리 없음

by kail 2022. 3. 19. 16:27

본문

디자인 패턴 한번씩 보면 가질 수 있는 의문들(1)

1. 추상팩토리패턴: abstract구조로 메소드를 한 곳에 모아서 보호하는 구조
장점: 한 곳에 모아서 보호하기 때문에, 결합력이 높다
단점: 확장성을 위해서는 오버라이딩과 자체함수를 강제하거나, 추상클래스라 하더라도 하나의 클래스에 너무 많은 기능을 부여한다
추가: 디자인패턴과는 상관없는데 abstract로 코딩했기때문에, 다중상속 불가능(즉 하나의 추상class에 하나의 기능을 부여해서 상속하는 관점이라면 위와같은 코딩이 어려움
물론 하나의 기능을 잡는 관점이 케이크를 보고 먹는 class라면 케이스를 먹는다는 인터페이스 대신
케이크같이 크게 잡으면 될 부분이기 때문에 중요한 요소는 아님 
애초에 여러개의 인터페이스의 기능을 상속받기보다는
케이크를 보고 먹는다는 그냥 메소드 단위로 묶으면 되고, 케이크를 보는게 어떠한 많은 제약이나 상황에 따른 조건이 존재하는 게 크다면
그 또한 케이크를 보는 class단위로 잡고 상속대신 케이크에서 케이크를 보는 클래스의 객체를 케이크라는 class를 접근하는게 오히려 일반적임
위와 같은 방식으로, 감당할 요소가 너무 커진다면 그걸 abstract class로 잘 설계해서 코딩하면 되니까 역량에 따라선 얼마든지 인터페이스 대신 대체가 가능하다고 생각함
근데, 이건 혼자 작업할 떄 그런것이고, 기획자가 확장안해도 되요 라고 말했는데, 아... 확장안해도 된다고 했는데 확장해주세요 이러고 있으면 인터페이스가 훨씬 좋음)
(물론 abstract로 혹시 몰라서 확장할 수도 있으니 class로 짜야지 이렇게 하면 되는데, 이렇게 abstract로 짜면 설계할 떄 머리에 힘을 좀 넣고 해야될듯)
굳이 abstract로 쓰는 이유는 protected를 사용할 수 있다는 점이 다른 패키지에서 마음대로 접근 못하게 막는다는게 무지막지하게 좋음... 넘사
interface로도 물론 짤 수 있음 블로그에 올려놨는데, 마음대로 생각해서 고르면 될듯)
2. 빌더패턴: 얘도 위에 친구랑 조금 비슷한데, 생성자를 통해서 받아야 한다면, 더 쉽게 말하면 setter를 안 써도 될 상황이면 생성자로 받을 때
그거 다 기억해야 되니까 함수로 정의하면 좀 쉽게 기억해서 짤 수 있으니까 쓰는 기능임
장점: 이클립스는 db로 값 받을 때처럼 자바로 db값 안 바꾸고 그냥 객체 넘기기같은 거만 할꺼면 안에 5개 값 이상이 들어오면 여러개 값 받을 때 처럼 setter 안 쓸꺼면 써라.  급인듯 근데 단점도 조금 있긴함 
단점: 생성자를 이용하면 값을 강제할 수 있음 근데 이렇게 짜면 
null 값이 들어올 수 있는건 함수를 빼먹으면 ㅈ되는듯? 그래서 필자는 
if문 같이 만약 들어올 값이 null이면 예외를 throws로 넘길 것을 주장함(이렇게 짜면 setter처럼 null로 보낼 위험이 없음)
예외처리에 대한 클래스를 따로 두냐에 대한 내용은 다음 블로그에 올림
단점2: 인텔리제이는 생성자 안의 매개변수 값이 자동으로 보여주기 떄문에, 딱히 위와 같이 짤 필요가 없음 컴파일러가 ㅈ사기라는 말이기도 함...
3. 팩토리메소드패턴: 생성할 떄 서브 class로 위임하는 패턴
생성할 때 객체를 직접 생성해줘도 되는데, 얘는 Factory같은 다른 class가 생성을 해줌 AppConfig 만들 때 위 패턴을 사용함
장점: DI 의존성 주입을 시켜주니 그냥 닥치고 무조건 써라.. 라고 밖에 할 말이 없음 시간 없어서 무지성 setter/getter 하드 코딩 하는 수준아니면 시간되면 그냥 무조건 하셈
단점: 딱히 없음. 굳이 있다면 시간 없을 떄는 설계해서 분리하고 같은 시간이 없으니, 원래라면 A <-> B랑 B<->C C<->A 라는 구조가 있으면 미디에이터 패턴으로 A<->D B<->D C<->D로 편하게 짜겠지만
시간이 없으니 public으로 각 데이터 바로 접근 수정해서 짜면 좀 더 빠르다는 거(안 썼을 때 좀 빠르니까 이거의 이익이 상황에 따라선 나을 수 있다는 점?)
4. 프로토타입 패턴: 이건 진짜 아무것도 없음. 깊은 참조 사용하는 패턴임
진짜 얘는 clonable 문법 코딩하는 느낌이라서 그냥 쓰지 않겠음 
첨언하면 깊은 참조를 사용하면 주소가 아니라 각 변수랑 메소드까지 하나하나 다 복사하고, 얕은 참조는 그냥 같은 주소 갖다 붙혀라 이런 느낌이라서 얕은 참조임
장점: 깊은 참조라 하나하나 다 복사하기 때문에, 여기서 그냥 값 복사하고 나중에 객체 값 비교하는 설계가 필요하다면 사용하면 좋음(얕은 참조는 복사가 어려움)
단점: 얕은 참조는 그냥 같은 주소 갖다붙히라는 느낌인데 얘는 새로 객체 생성해야되니 시간이 느려짐
5.싱글턴: 얘는 생성하고 이후 이 메모리 사용할 떄 같은 메모리주소를 참조하도록 강제하는 느낌임 우회법도 있긴한데, 하는 사람이 설마 있을 까 싶어서 그냥 말 안함(그런거 우회할 수준이면
이미 기존의 코드대로 짤듯)
장점: 하나의 객체를 사용하기 떄문에 빠름
단점: 내부 코드를 테스트하기 어렵고, 유연성이 떨어진다(물론 알고리즘은 싱글스레드를 기준으로 해서 조금 다른 얘기일 수도 있지만, 암튼 알고리즘할 떄 어렵고 빠르게 짜는 것보다 어느정도
알기 쉽게 짜려고 하는 걸 생각하면 좋은 것 같다. 비슷하게 db도 분리안하고 짜면 더 빠르다는 얘기도 있던데, 분리해서 하면 그 대신 db단이랑 분리가 더 잘되서 분리한다는 얘기를 들어 본 적이 있고,
빠르면 c++을 위주로 하면 되는데, java단은 좀 더 보기 편한 걸 위주로 아는 성향이 있다
그래서 cpp쪽은 좀 더 저렇게 사용하는 걸 지향하겠지만, java단은 저런 걸 짠다면 주변 사람들이 어렵더라도 얼마나 잘 사용하는지 "보편성에 대한" 생각을 해봐야될 것 같긴하다
그런점에서 자체 난이도 보다는 알고리즘단처럼 좀 보기 어려우면, 굳이 이걸 이렇게 짜나 라는 생각이 주변 환경에서 많다면 줄이고, 
싱글톤처럼 좀 어려울 수 있어도, 많이 처리를 한다라는 얘기가 나오는 코드면 지향하는 게 내 생각에서 java를 효율적으로 짜는 것이 아닌가 싶다)
6. 어댑터: 두 개의 일정한 상황에서만 가능하게 정의 된 인터페이스들이 이미 있다면 이걸 유지보수할 떄 중간에 이을 수 있도록 인터페이스랑 구현체를 하나 만들어서 잇는 방식
(인터페이스 유무는 그냥 확장성 생각해서, 혹시 모른다 싶으면 추가하고, 어차피 그냥 연결하는건데 이건 이거대로 연결하고 다른 것중에 연결되어있는 것도 있고
어차피 별로 일치할 만한 게 없어보이니 그건 그거대로 다른 어댑터 패턴을 만들자 싶으면 만들고 알아서 하면 될 것 같다)
7. 브릿지 패턴: 그냥 인터페이스랑 구현체를 분리하는 패턴
장점: 확장하거나 교체할 수 있는거 인터페이스랑 구현체 따로만들어서 분리하면 확장성 증가
단점: 확장 할 필요없는거는 굳이 늘릴 필요는 없는 것 같아, 불필요하다. 그러니까 치킨먹는class랑 피자먹는 class가 있는데, 여기서 그냥 각 class마다 먹는 게 공통적이라면
먹는 함수만 오버라이딩 하고 void eat(){sout(치킨먹기)sout (피자먹기)}
이러면 되는데, 굳이 치킨이랑 피자를 음식이라는 인터페이스의 구현체로 늘릴 필요는 없다
(그러니까 치킨먹는 class,피자먹는 class에 음식interface(안에 아무것도 없음),음식먹는class를 굳이 할 필요는 없다)
8.컴퍼지트 패턴 
트리 구조를 객체끼리 연결하게 하는 패턴
장점: 연결리스트를 탐색할 때 (이터레이터 패턴을 사용할 때)는 중간부터 탐색을 한다면 다 탐색을 해야한다면 불편한데, 이 친구는 중간 부터 탐색할 수 있도록 설계하기 편하다
단점: 객체 연결할 떄 이터레이터 패턴은 간단하게 add함수 만들어서 자동으로 다음 객체 전달하도록 연결하면 되는데, 이 친구는 연결할 떄 수동으로 File1.add(File2)이딴식으로 연결해야한다
9. 데커레이터 패턴
동적으로 함수를 추가할 때 사용하는 패턴
장점: 동적으로 함수를 오버라이딩 해서 넘겨주니까, 시간이 없어서 하드코딩으로 빠르고 유지보수 편하게 짜고 싶은 게 조건이면 이게 맞을 수도 있다
단점: 저런 상황이 나올 수 있는지 사실 잘 모르겠다. 유지보수 편하게 짜려고 값 넘기는 게 익숙해지면 솔직히 저게 나을 수도 있다
장점만 보면 많아보이지만 사실 그렇게 많이 필요할 것 같지는 않다
예를 들어 커피를 판매하는데 원재료인 우유라는 class가 ml당 가격이랑 이런거 상황에 맞게 선택해야 되고 커피콩도 class라g당 가격 재료 얼마나 필요한지 각 재료 사용량이 어느정돈지
해당 재료에 안 좋은 뉴스가 있는지
이런거 상황에 맞게 어느제품을 구매할지 선택해야되고
점원이 가격을 올린다던가 이벤트 한다던가 하는 서비스까지 따로 좀 있으면 class 우유 class 커피콩 이렇게 따로 둬야하고,
가령, 커피를 판매하면 커피콩가격이 오르거나, 우유가격이 올라가면 여기서 그냥 커피{} class 우유{if(우유 사기위한 조건==true) {return true;} 우유 함수(){return 우유 가격;}} 
class설탕 {if(설탕 사기위한 조건==true) 설탕함수(){return 설탕 가격;}}
머 이런 조건이 다 맞아야 한다
만약, 우유원재료 가격만 따로 추가해도 되면 그냥 커피class에 우유 원재료가격이랑 이걸 다 커피안에선언해서 자체 서비스값이랑 더하고 
같은 정적 class형식으로 하는게 더 빠르고 위의 경우는 확장성 고려할 필요는 없기 떄문이다
만약, 시간이 남는다면 동적으로 넘길 생각보다는 따로 사기위한 조건을 단위로 class를 둬서 거기에서 처리하도록 하는 것이 훨씬 잘 짜여진 설계이기 때문이다
10. 퍼사드 패턴
복잡하거나 반복적인 과정을 간단하게 사용하게 하는 패턴
그냥 계산기 있으면 34+3을 할 떄 내부적으로 0과1어떻게 컴퓨터에서 처리하는지 코딩안하고 return 34+3; 이런식으로 간단하게 코딩하는 것처럼 
함수 만들 때 반복호출해야되거나 복잡한 걸 그냥 하나의 함수에 묶어서 그거 쉽게 하나의 class 만들어서 그냥 거기서 간단한 함수로 처리하게 해주는 것(하나의 함수로 해도 되는데 디자인패턴은
oop 패턴방식인거 생각)
11. 플라이웨이트 패턴
그냥 메모리주소참조할 떄 같은 메모리주소를 참조할 수도 있게끔 만들어주는 패턴
장점: 싱글톤처럼 하나의 같은 메모리 사용을 하기 때문에 좋다
단점: 단점은 잘 모르겠고, 싱글톤은 마음대로 다루는 것을 엄격하게 막아주지만, 이 친구는 호출할 때 좀 풀어주는 느낌이다
12. 프록시 패턴
장점: 객체에 있는 값을 직접 안바꾸고 프록시(서브 클래스)로 바꾸기 떄문에 하나의 클래스에서 너무 많은 처리를 한다면 이를 분리해줄 수 있다
단점: 얘도 잘 쓰면 좋은 것 같다 시간이 없어서 하드코딩해야되면 얘는 따로 분리해야되니 시간이 필요해서 시간이 없다면 후순위로 해야되는 게 낫다.
13. 책임 연쇄 패턴
장점: 자신의 패턴에서 처리가 안된다면 넘겨주면서 분리하니까 하나의 클래스에 너무 많은 역할을 담당한다면 분리해줄 수 있다(다른 class라 넘기는 방법을 말하고 있다)
단점: 프록시랑 비숫허개 하드 코딩해야되면 따로 분리해야되니 시간이 없다면 후순위로 하는게 낫다
유지보수 할 때 기존의 코드를 안보고 따로 처리해서 늘려주면 되니까 위 패턴은 기존의 코드가 잘 짜여있는데 어떠한 조건에서만 안된다면 위 방식을 차용하는 것을 추천한다
프록시는 객체에 있는 값 다른 class로 쉽게 수정하려고 쓰는 용도. 책임 연쇄 패턴은 A안되면 B로 B안되면 C로 처리하는 용도 
14. 인터프리터 패턴
코딩 언어 만들면, 문법 같은 걸 class단위로 묶는 패턴
장점: 간단한건 그냥 이렇게 만들면 된다
단점: 언어에서 실행시킬 용도라면 복잡한건 언어에서 언어를 만드는 것이기 때문에, 고대언어에서 만들고 그걸 java같은 언어에서 프로그램만 실행시키도록 하는 방법을 추천한다
15. 이터레이터 패턴
장점: 이터레이터 만들어서 class에 자동으로 연결된 것들에 한꺼번에 먼가를 더하던가 또는 추가하던가 같은 걸 자동으로 할 수 있다
단점: 전에 이터레이터 패턴으로 엄청 복잡한 걸 추천해준 적이 있는데, 그런 경우는 컴파지트 패턴으로 구현하면 수동적으로 하나하나 add로 직접 늘려줘야되서 컴파짓도 되지만 이터레이터 패턴으로 짜는게 맞다
16. 미디에이터 패턴
장점: 위에서도 말했는데 A<->B B<->C C<->A 이렇게 각각이 각각을 참조하는 구조라면 A<->D B<->D C<->D 이런식으로 짜는게 훨씬 보기 편하다
단점: 위 패턴으로 만드는데 걸리는 시간
17. 메멘토 패턴
각각의 메모리를 저장해놓고 나중에 다시 그것으로 되돌리거나 할 수 있는 패턴
장점: 사실 이 친구도 프로토타입 패턴과 다른 방식으로 비슷한 퍼포먼스를 낼 수 있는데 A기억하고 B로 A를 추가해서 B쓰다가 A클래스로 다시 참조하거나 하면 되는데,
clonable쓰면 되고, A,B,C,D,E,F 각각 저장하려고 하면 clonable로 B,C,D,E,F를 계속만들어야되니까 그럴 땐 메멘토 패턴으로 미리 백업처럼 저장해 두는 게 맞는 것 같다(stack으로 넣어서 쉽게 
가져다 쓸 수 있음)
단점: 2개 비교하고 끝낼 때 스택으로 메모리 저장해도 되기는 할 것 같은데, clonable이 좀 더 직관적이라고 생각한다 물론 메모리 측면에서는 이 쪽이 조금 더 좋은 것 같긴하다
(깊은 복사하려면 다 만들어야되는 반면, 이 친구는 스택만 만들면 된다)
18.옵서버 패턴
클래스에서 인자를 받을 때 인터페이스로 받아서 구현체의 기능을 쉽게 이용할 수 있도록 사용하는 패턴
좀 더 풀어서 설명하면
A라는 class랑 B라는 class가 있다면 여기서 B에서 A라는 class를 사용하려면 생성자에서 A라는 class를 인자로 받으면 되는데,
이런 경우 생기는 단점은 A라는 class에 있는 변수를 막 접근할 수도 있다. 그리고 만약 A대신 service의 같은 기능을 하는C라는 class로 사용을 바꾸고 싶다면
A대신 C로 인자를 바꾸고, 값을 보낼 때도 A로 보내지 말고 C로 보내게 짜야해서 비효율적이다
근데, 이걸 service의 기능을 인터페이스로 만들고 A랑 C를 인터페이스의 구현체로 만들면
인터페이스로 보내면 인터페이스의 구혀A대신 C로 보내는 것만 바꾸면 바로 작동하므로 효과적이게 작동한다
이렇게 인터페이스로 인자를 보내는 방식을 옵서버 패턴이라고 한다
파사드 패턴이랑 잘못 설명한적이 있었는데, 파사드 패턴은 복잡하거나 반복작업을 함수한번 호출로 간단하게 하도록 하는 것
옵저버 패턴은 쉽게 가져다 쓸 수 있도록 "인터페이스로 값을 보내주는 방법"이다
스테이트 패턴: 각 객체마다 동적으로 객체 상태가 A면 A 형태로 B면 B형태로 자주 바뀌면 효과적인 방법이다
장점: 예를 들어 A라는 사람이 한 게임 내에서 검으로 공격도 되고, 활로도 공격이 되는데 무기를 계속 이거 썼다가 저거썼다가 하면 스트레티지(전략패턴)은 attack()메소드를 오버라이딩 해서
A를 객체로 생성했다가 B를 객체로 생성했다가 이걸 계속 호출하면서 계속 바꿔야된다
근데, 스트레티지 패턴은 그 조건을 시간에 따라서 검공격 활공격 하는 걸 계속 바꿔지게 하거나, 아니면 검썼다 활썼다 이걸 계속 바꾸려고한다면 change.sword() change.bow()만 호출해서
객체만 바꾸면 되니 효과적이다
단점: 검으로 공격했다 활로 공격했다 이런게 한번 호출하고 말것이면, 굳이 바꾸는 걸 메소드로 하기 보단 무기라는 인터페이스에 각 무기를 구현체로 사용하는 게 맞다
예를 들면, 게임 캐릭터 고를 떄 무기선택해서 듀얼소드 캐릭터고르면 듀얼소드검만 써야한다면 이 경우는 다른 캐릭터로 바꾸기전까지 평생 유지되므로 스트레티지(전략 패턴)이 더 효과적이다
템플릿 메소드 패턴: 
나중에 함수를 오버라이딩 할 때, 기존의 부모 class에 있는 것과 자식class에 있는 메소드를

오버라이딩 해서 이용하는 패턴이다

(전에 자료를 찾다가 잘못 알려준 것 같은 느낌이 들어 위와 같이 수정하였다.)

비지터 패턴
행위자랑 행동을 구분하는 패턴
그냥 행위자랑 행동에 초점을 맞춰서 짜는 패턴 방식이라 행위자랑 행동에 관한 것이 많으면 거기에 따라 다른 class로 구분하는 패턴이다
장점: 예를 들어 캐릭터 커스터마이징 할 때 A라는 사람이랑 B라는 댄스 동작이 있다면
3D 캐주얼 같은 게임을 A라는 사람 B라는 사람 C라는 사람이라는 사람틀이랑 3d 사람 인체에 나사만 끼워서 거기에 맞춰 동작을 수행한다
그러니까 목을 흔드는 댄스 라면 A라는 사람 B라는 사람 C라는 사람 틀만 만들고 목흔드는 댄스의 class를 만들어서, 거기서 각 행위자 class의 목을 흔드는 동작을 취하는 함수를 만들면 된다
단점: class는 너무 많아지면 역으로 불필요하다. 그래서, 위와 같이 행위자랑 행동을 구분해야하는 확장성이 필요하다면 그거에 맞게 분리하면 될 뿐이라고 생각한다