1. 요약

GoF 중 생성 패턴으로 프로토 타입 패턴은 Clone을 이용하여 객체를 생성하는 방법입니다. 추상 팩토리 패턴의 경우 객체를 생성하는 코드를 파생 클래스에서 작성을 하였다면, 프로토 타입 패턴의 경우 파생 클래스에서는 객체를 생성하는 코드가 별도로 만들어지지 않습니다. 기존에 생성된 객체를 이용하여 해당 타입의 객체를 생성하게 됩니다. 

 

2. 프로토 타입 패턴

프로토 타입 패턴을 이용하기 위해서는 Prototype이 되는 추상 클래스가 필요합니다. 그리고 해당 클래스에서는 Clone이라는 메서드를 추상으로 선언하게 됩니다. 그리고 나머지 파생 클래스에서는 Clone 함수를 구현하게 됩니다. 그리고 실제 객체가 필요한 경우 PrototypeFactory를 이용해 필요한 객체를 만들 수 있습니다. 객체를 구분하는데 여러가지 방법이 있습니다. enum을 사용하거나, 객체별 id를 부여하거나, type를 비교할 수도 있습니다. 이렇게 객체의 타입을 구분할 수 있는 방법을 하나 선정하여서 이미 프로토 타입으로 인스턴스화된 객체로부터 새로운 객체를 가져오게 됩니다. 여기서 C#을 기준으로 예제 코드가 만들어지는데요. MemberwiseClone 이라는 함수를 사용하게 되는데요. 해당 함수는 객체를 복사한 새로운 객체를 object 타입으로 반환해주는 함수입니다. 

3. 예제

가장 기본이 되는 ProtoType 클래스입니다. 해당 클래스는 추상으로 되어있고, 여기에 id나 다른 변수와 메서드를 추가할 수 있습니다. 그리고 이 추상 클래스를 상속 받아 ConcreteProtoType1, 2, 3 .... 여러가지 파생 클래스를 생성할 수 있습니다. 현재는 생성된 클래스에 모두 Clone 함수를 재정의 해줘야합니다.

이런식으로 MemberwiseClone 함수를 이용해서 별도의 생성자를 통한 인스턴스를 생성하는게 아닌 기존의 객체를 복사하게 됩니다. 그렇다면 이제 ProtoType Factory를 만들어보도록 하겠습니다. 

Generic 을 활용하여 Type을 직접 입력 받아서 해당 타입에 맞는 객체를 생성하는 Factory 입니다. Factory 생성시 프로토 타입에 해당하는 인스턴스를 미리 만들어서 등록하는 것이 포인트입니다. 이렇게 되면 생성자에서 생성된 인스턴스를 기즌으로 새로운 객체들이 생성되게 됩니다.

실제 Main 함수에서는 이와 같이 만들어지게 됩니다. 이 방법 외에도 enum을 활용하거나 index를 활용하는 방법이 있는데.. 저는 위의 방법을 가장 선호합니다. 가장 직관적이고 별도로 enum이나 index를 생성하지 않아도 구현이 가능하기 때문입니다. 이렇게 구현을 하게 되면 향후 유지보수에 유리하게 됩니다.

1. 요약

빌더 패턴은 생성 패턴의 한 종류로 복잡한 단계로 생성되는 생성자를 더 쉽고 가독성이 높게 생성할 수 있도록 고안된 패턴입니다. 대부분의 디자인 패턴이 그러하듯 기본적인 개념을 갖고 더 좋고 편리하게 확장할 수 있습니다. 빌더 패턴의 경우 기존 생성자를 통해서 객체를 생성하는 방식이 아닌, 객체를 생성하는 클래스를 만들어 해당 클래스에서 인스턴스를 생성하므로 가독성과 유지보수가 쉽도록 고안된 패턴입니다.

2. 빌더 패턴

빌더 패턴에서는 빌더라는 클래스가 필요합니다. 빌더 클래스는 해당 객체의 값을 설정하고 다시 반환하는 역할을 합니다. 예를 들어 학생의 점수를 기록하는 객체가 있다고 할때, 과목이 10개가 있다고 하면 각 과목마다 점수를 저장하는 변수가 있을 것이고 이 변수에 값을 하나하나 입력을 해야합니다. 이런 경우 각 과목의 점수를 모두 생성자를 이용해서 입력을 한다거나, 직접 접근해서 점수를 입력할 수 있지만 이런 경우 새로운 과목이 추가될 경우 변경사항을 적용하는데 귀찮음이 생기게 됩니다. 따라서 이런 경우 각 과목의 점수를 입력하는 Builder를 만들어 해당 Builder에서 점수를 기록하는 방식으로 변경하게 된다면 기존의 방식보다 훨씬 더 가독성이 좋고 유지보수하기 쉽게 변경할 수 있습니다. 빌드 패턴을 이용하는 목적은 편리함과 가독성에 중점을 두고 있기 때문에 성능상에 큰 차이는 없지만 작업능률에 큰 차이가 있을 수 있습니다. 그렇다면 이제 학생들의 점수를 기록하는 빌더 패턴을 코드를 보면서 예제를 만들어보도록 하겠습니다. 

3. 예제

해당 예제는 개념적인 설명을 위해 아주 간단하게 작성하였고, 추가적으로 다양한 개념을 추가하여 자신만의 빌더 패턴을 완성할 수 있습니다...

우선 학생들의 점수를 저장하는 클래스가 필요합니다. 해당 클래스의 이름은 StudentScore라고 하고 해당 클래스에는 수학(Mat), 영어(Eng), 국어(Kor), 사회(Soc), 과학(Sci), 기술(Ski), 체육(Ath), 미술(Art), 음악(Mus) 이렇게 과목이 있다고 가정하겠습니다.

생성자는 다음과 같이 만들 수 있습니다. 생성자 내부는 귀찮아서 패스하겠습니다. 이렇게 많은 값들을 입력을 해야지 객체를 생성할 수 있습니다. 따라서 입력을 할때 자신도 모르게 실수를 하거나 중간에 입력을 추가하게 되는 경우, 혹은 입력값이 바뀌는 경우 이를 변경하기 쉽지 않습니다. 따라서 이런 경우 빌더 패턴을 이용하여 쉽게 객체를 생성할 수 있습니다. 

해당 빌더에서는 StudentScore 라는 객체를 들고 있고 하나의 빌더가 생성될때 StudentScore가 같이 생성이 됩니다. 그리고 각 과목의 점수를 설정할때마다 값이 설정되고 설정이 된 빌더 객체를 반환하도록 설계되어 있습니다. 이런 경우 각 과목마다 값을 설정하는 함수를 만들어 값을 설정하기 때문에 생성자를 사용해서 값을 설정하는 것보다 더 직관적으로 값을 설정할 수 있기 때문에 문제를 덜 만들게 됩니다. 그렇다면 실제 값을 설정하여 인스턴스를 생성하는 과정을 보도록 하겠습니다.

이런식으로 객체를 생성할 수 있고 여기에 Builder 객체에서 ToBuild() 라는 함수를 추가해서 설정한 StudentScore 객체를 반환 받으면 Builder를 이용하여 복잡한 객체를 생성할 수 있게 되는 것입니다. 

지금까지 설명한 빌더 패턴의 기본 구조를 유지한 상태로 더 다양한 기능을 추가하여 자신만의 빌더 패턴을 만들 수 있습니다.

1. 요약

팩토리 메소드 패턴은 생성패턴의 한 종류로 객체를 생성하는 방법을 해당 클래스를 상속 받은 클래스에서 명시하도록 하는 패턴입니다. 일반적으로 클래스에서 객체를 생성하기 위해서는 생성자를 만드는게 일반적인데요. 팩토리 메소드 패턴은 이런 행위를 위임하게 됩니다. 

2. 팩토리 메소드 패턴

팩토리 메소드 패턴을 이해하기 위해서는 추상 클래스에 대한 이야기부터 해야합니다. 추상 클래스는 아래 링크에서 찾아볼 수 있습니다. https://math-development-geometry.tistory.com/40?category=708726

 

C# - 추상 클래스, 가상 클래스 - abstract, virtual 사용법

지난 포스팅 인터페이스에서 잠시 언급된 추상과 가상에 대한 이야기를 하도록 하겠습니다. 추상과 가상을 먼저 이야기 했으면 좋았겠지만, 제 포스팅은 그냥 의식의 흐름으로 적는 포스팅이기 때문에 순서가 엉망..

math-development-geometry.tistory.com

추상 클래스는 해당 클래스의 기능을 구현할 수 있기도 하지만 파생 클래스에서 해당 기능을 직접 구현할 수 있도록 인터페이스를 제공하는 기능을 하게 됩니다. 여기서 팩토리 메소드 패턴을 사용하기 위해서는 메소드를 추상으로 선언하여 해당 클래스를 상속 받은 파생 클래스에서 생성하는 기능을 구현하게 됩니다. 이렇게 되면 기본적인 구조가 잡힌 상태에서 여러 사람이 각자 파생 클래스를 구현하게 되면서 협업이 가능하게 됩니다. 또 기능이 바뀌거나 새로운 기능을 추가할때 해당 객체만 변경하면 되기 때문에 객체를 생성하는 코드가 유연하게 변경할 수 있는 장점이 있습니다. 그리고 필요한 경우 증복 기능을 묶어 다른 클래스로 생성하여 구조를 잡을 수 있습니다. 

일반적으로 팩토리 메소드 패턴의 경우 Product, ConcreteProduct, Creator, ConcreteCreator로 4가지로 나눠 구현을 하게 됩니다. Product는 팩토리 메서드로 생성될 공용 객체입니다. 그리고 ConcreteProduct 는 Product를 상속받아 생성되는 클래스입니다. ConcreteProduct는 필요에 따라서 생성이 가능합니다. Creator는 Product를 생성하는 메서드가 있는 클래스입니다. ConcreteCreator는 팩토리 메서드를 구현하는 클래스로 Creator를 상속받아 각각의 ConcreteProduct를 구현하는 메서드가 명시되어 있습니다.

  Product Creator
- Product Creator
Concrete ConcreteProduct ConcreteCreator

4가지를 구분하는 방법은 Product는 실제 객체 클래스를 표현하고 Creator는 생성되는 메서드가 있는 클래스입니다. 그리고 - 는 상속을 할 클래스이고 Concrete는 -를 상속 받은 클래스입니다. 

3. 예제

해당 예제를 통해서 자세하게 알아보도록 하겠습니다. 해당 코드는 삼각형, 사각형과 같은 도형을 표현하는 클래스를 팩토리 메서드 패턴을 이용하여 제작한 것입니다. 

Shape은 모든 도형의 파생 클래스가 됩니다. 팩토리 메소드 패턴의 Product가 됩니다. 그리고 Shape을 상속 받아서 다양한 도형을 만들 수 있습니다. 그렇게 만들어진 도형이 ConcreteProduct입니다.

이제 Creator 클래스입니다.

그리고 Circle과 Triangle를 생성하는 Creator는 아래와 같습니다. 

예제 코드는 아주 간단한 코드만 작성한 것인데요. 이를 좀 더 자세하게 작성하면서 생성하는 코드와 실제 코드를 구분하며 객체를 생성하는 Factory를 따로 작성하는 것입니다.

1. 요약

싱글톤 패턴은 아주 간단하거나 단순한 프로그램을 만들때 유용하게 사용할 수 있는 디자인 패턴입니다. 싱글톤 패턴을 사용하게 되면 모든 데이터를 전역으로 관리할 수 있기 때문에 쉽게 접근할 수 있다는 장점이 있습니다. 하지만 이는 복잡하고 체계적인 프로그램을 만드는데 다양한 문제를 만들 수 있는 가능성이 있는 디자인 패턴으로 조금해서 사용해야 합니다. 

2. 싱글톤 패턴

싱글톤 패턴은 해당 객체의 메모리를 정적으로 할당하여 하나의 객체에만 접근하는 방법입니다. 따라서 프로그램이 동작하는 동안 최초로 생성된 객체 하나에만 접근하게 되므로 데이터를 접근하고 수정하는데 아주 용이한 특징이 있습니다. 그리고 생성자를 다른 곳에서 새롭게 선언을 하더라도 이미 정적으로 선언된 객체가 반환되기 때문에 중복되어 생성되는 것을 방지 할 수 있습니다. 그리고 초기 객체를 생성을 하게 되면 정적 메모리에 올라가기 때문에 이후 호출하는데 아주 빠르게 접근할 수 있는 장점이 있습니다. 

하지만 싱글톤 패턴을 사용할때 문제점도 많이 생기는데요. 우선 정적 메모리에 할당된 객체로 해당 객체에 너무 큰 메모리가 쌓이게 되면 프로그램 성능이 현저하게 낮아질 수 있습니다. 이는 정적 메모리에 할당할 수 있는 메모리 크기가 제한적이기 때문입니다. 그리고 프로그램이 복잡해지면서 서로 다른 데이터를 공유하게 되는데 이때 다른 객체들과 결합도가 낮아지게 됩니다. 이는 디버깅 과정 등 협업을 하는데 어려움이 됩니다. 또 하나의 정적 메모리를 사용하기 때문에 병렬처리나 동기화와 같이 여러방법으로 메모리에 접근하는데 문제가 생기게 됩니다.  

따라서 싱글톤 패턴을 사용하는데 있어서 그 용도와 한계를 명확하게 알고 사용하는 것이 중요합니다. 싱글톤의 경우 병렬처리나 동기화를 위해서 사용되는 방법이 따로 존재하지만, 이는 프로그래밍 언어가 발전하면서 다양한 기법들이 나오면서 가능해진 것입니다. 따라서 싱글톤과 멀티쓰레드를 동시에 사용할 일이 있다고 하더라도 이를 둘다 선택하는게 아닌 다른 방법이 있는지 찾아보는 것이 더 좋은 방법이 됩니다.   

 3. 예제

지금부터 싱글톤 패턴이 어떻게 사용되는지 실제 코드를 가지고 이야기하도록 하겠습니다. 

Singleton으로 클래스를 생성한 기본 코드입니다. Singleton 이라는 클래스 이름을 갖고, 내부에는 static 으로 객체를 하나 생성합니다. 그리고 static 함수로 해당 객체의 정적 객체를 반환하는 함수를 만들었습니다. 그리고 해당 객체가 null 인 경우에만 다시 생성을 하게 됩니다. 따라서 한번 생성된 객체가 null 이 되기 전까지는 기존에 생성된 static 객체를 사용하게 됩니다.

실제 Main 함수에서는 Singleton.Instance() 함수를 이용해서 생성된 객체에 접근할 수 있습니다. 그리고 objectA, objectB, objectC를 생성하더라도 각 객체는 모두 같은 객체입니다.

1. 요약

디자인 패턴은 소프트웨어를 설계하면서 자주 발생하는 문제들을 해결하기 위한 해결 방법을 일반화하여 정리한 것을 말합니다. 그래서 프로그램을 개발하면서 이미 많은 사람들이 고민했던 고민들을 쉽게 해결할 수 있다는 장점이 있고, 이를 이용하면 빠르게 프로그램을 개발할 수 있다는 장점이 있습니다. 그리고 이미 정리된 패턴을 사용하게 되면 팀원들과 소통이 빠르고 정확하게 이루어진다는 장점이 있습니다. 또 기존에 존재하는 패턴에 자신만의 기능을 추가하여 새로운 패턴을 만드는 것도 가능합니다. 

2. 종류

디자인 패턴으로 가장 유명한 GoF 디자인 패턴이 있습니다. GoF는 (Gang of Fout)으로 Erich Gamma, Richard Helm, Ralph Johnson, John Vissides 라는 사람들이 소프트웨어를 개발하면서 생기는 문제들을 구분하고 체계화하여 정리하였습니다. 이들은 23개의 디자인 패턴을 정리하고 이 23개의 패턴을 크게 3가지로 분류했습니다. 생성(Creational)패턴, 구조(Structural) 패턴, 행위(Behavioral) 패턴입니다.

1) Creational Pattern

생성패턴은 객체를 생성하는데 필요한 패턴들입니다. 객체의 생성을 캡슐화하여 전체적인 프로그램 구조에 영향을 주지 않으면서 확장성을 갖게 만든 패턴입니다. 아래와 같은 패턴이 생성패턴입니다.

팩토리 메서드 패턴(Factory Methods Pattern) / 추상 팩토리 패턴(Abstract Factory Pattern) / 싱글턴 패턴(Singleton Pattern) / 빌더 패턴(Builder Pattern) /  프로토타입 패턴(Prototype Pattern)

2) Structural Pattern

기존의 객체를 확장하거나 여러개의 객체를 이용하여 더 큰 객체나 구조를 형성하는 패턴입니다. 아래와 같은 패턴이 생성패턴에 속합니다.

어댑터 패턴(Adapter Pattern) / 브릿지 패턴(Bridge Pattern) / 컴포지트 패턴(Composite Pattern) / 데코레이터 패턴(Decorator Pattern) / 퍼사드 패턴(Facade Pattern) / 플라이웨이트 패턴(Flyweight Pattern) / 프록시 패턴(Proxy Pattern)

3) Behavioral Pattern

행위패턴은 객체나 클래스에서 행동을 묘사하는 패턴입니다. 하나의 행동이 여러 행동으로 영향을 주던지, 하나의 객체에서 여러 행동을 하는 것과 같은 기능을 하게 됩니다.

책임 연쇄 패턴(Chain of Responsibility Pattern) / 커맨드(Command Pattern) / 인터프리터 패턴(Interpreter Pattern) / 이터레이터 패턴(Iterator Pattern) / 미디에이터 패턴(Mediator Pattern) / 메멘토 패턴(Memento Pattern) / 옵저버 패턴(Observer Pattern) / 스테이트 패턴(State Pattern) / 스트래티지 패턴(Strategy Pattern) / 템플릿메서드 패턴(Template Method Pattern) /  비지터 패턴(Visitor 패턴)

 

가상이나 추상 인터페이스 같은 키워드를 사용하여 클래스 구조를 잡다보면 너무 복잡해지고 다른 사람이 내가 만든 클래스를 사용하다보면 혼란을 가져오게 됩니다. 물론 클래스 설계가 잘 되어있고 해당 문서가 있다면 더 좋겠지만, 개발하면서 이런 문서까지 만든다는 것은 쉽지 않은게 사실입니다. 따라서 이런 문제를 조금이나마 해결하기 위해 사용하는 sealed class에 대해서 알아보도록 하겠습니다. 이전에 가상, 추상에 대한 포스팅과 인터페이스에 대한 포스팅은 아래 있으니까 참고하세요.

https://math-development-geometry.tistory.com/40

불러오는 중입니다...

https://math-development-geometry.tistory.com/39

 

C# - interface 인터페이스 사용법과 원리

인터페이스는 상속 받을 클래스에 기능이나 속성을 지정하는 역할을 합니다. 일반적인 상속관계와 비슷하다고 볼 수 있지만 살짝 다른 개념입니다. 우선 C#에서는 C++과 다르게 하나의 부모 클래스만 상속 받을 수..

math-development-geometry.tistory.com

sealed는 한국말로 "봉인된"이라는 뜻을 갖고 있죠. 즉 클래스를 봉인시키는 키워드인데요. 상속으로부터 봉인시키는 것입니다. 만약에 어떤 클래스에 sealed 키워드가 있다면 더 이상 상속이 불가능하다는 것을 표현하는 것입니다. 그리고 이 sealed는 클래스 뿐 아니라 속성이나 함수에서도 사용이 가능합니다. 클래스에 sealed가 없고 함수나 속성에 sealed가 있다면 해당 클래스를 상속 받은 클래스에서는 함수나 속성을 재정의 하는 것이 불가능합니다. 한번 코드를 통해서 알아보도록 하겠습니다. 

추상, 가상에 대한 포스팅해서 사용했던 예제인데요. 롤 캐릭터를 구현하는 방법을 예시로 만들었습니다.

추상 클래스로 BaseCharacter가 있고 저번 시간에 없었던 Hp와 Mp가 추가되었습니다. 이제 이 BaseCharacter를 상속 받아서 다양한 캐릭터를 구현해주었었죠. CharacterA에 대해서 코드를 보도록 하겠습니다.

이번 예제에서는 저번과 다르게 Attack과 Stop 함수에 sealed 라는 키워드가 추가된 것을 볼 수 있죠. 만약에 CharacterA를 상속받아 만든 다른 캐릭터의 경우 Attack과 Stop에 대한 기능은 재정의가 불가능하게 됩니다. 이런 식으로 한 클래스안에서도 다양하게 기능을 제한할 수 있기 때문에 여러 사람들이 함께 작업을 하는 경우 실수를 줄이고 처음 설계된 구조로 개발하는데 도움이 됩니다. 사실 제가 지금까지 개발을 하면서 sealed를 사용하는 사람들을 잘 못 봤는데요. sealed는 구조가 확실히 잡힌 프로젝트이거나 확실히 봉인 시켜줘야할 필요가 있다면 사용하는 것이 장기적으로 볼때 좋아요. 상속이 여러번 일어나면서 계층이 깊어지게 되면 나중에 코드를 구현하는 사람 입장에서는 어디까지 재정의를 해야하는지 파악하기 쉽지 않고, 불필요한 기능까지 구현을 하기 때문에 문제가 생길 수 있습니다. 따라서 구조를 잡는 입장에서 확실하게 sealed를 사용하는 습관을 가지면 좋을 것 같네요.

+ Recent posts