코드의 확장성과 재사용성을 높이는 디자인 패턴이다.
부모 클래스로 객체를 생성하는 방식으로, 자식 클래스가 객체의 타입을 바꿀 수 있는 디자인 패턴이다.
만약 트럭이라는 클래스로 트럭 객체를 관리하고 있다고 하자. 그런데 트럭이 아닌 배로 물품을 수송할 경우가 생겼다. 트럭이라는 클래스로 수송 함수를 관리하고 있었는데 수송 함수는 같게 쓰이지만 다른 property를 가진 객체를 또 추가해야한다면 기존 코드의 많은 구조를 바꾸어야 한다.
여러 하위 클래스들이 가질 공통 속성을 수퍼클래스로 정의하고, 수퍼클래스를 상속하는 하위클래스가 필요한 성질을 정의하도록 한다.
class UserModel(BaseModel):
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
username: str = Field(default=None)
OU: str = Field(default=None)
admin: bool = Field(default=None)
# mongodb에서 foreignkey를 사용하려면 한 필드를 업데이트할때 모든 유관 필드를 업데이트해줘야 한다는 단점이 있음
# password: str
class Config:
orm_mode = True
allow_population_by_field_name = True
arbitrary_types_allowed = True
json_encoders = {ObjectId: str}
shcema_extra = {
"example": {
"username": "John Doe",
"OU": "example",
"admin": False,
}
}
class UserLoginModel(UserModel):
username: str = Field(default=None)
password: str = Field(defualt=None)
class Config:
the_schema = {
"user_demo": {
"username": "user_demo",
"password": 123,
}
}
실제로 내가 만든 FastAPI 로그인과 관련된 함수를 찾아왔다.
usermodel과 userloginmodel이 있는데, usermodel은 유저를 생성할때, userloginmodel은 유저가 로그인할때 사용하는 모델이다.
UserLoginModel이 UserModel을 상속하므로 UserModel 안의 모든 instance를 가지고 있지만 (e.g. admin, OU 등) 막상 로그인할때 사용하는 것은 아이디와 패스워드 뿐이다.
이런 상속을 통해 부모 클래스의 property를 그대로 가져오면서 password등 필요한 부분을 확장해 쓸 수 있다.
구체적인 클래스에 의존하지 않고 관련 객체들을 만드는 방법.
소파, 의자, 테이블 등 여러 종류의 가구를 생산해야 하는데, 각 가구에도 여러 디자인이 존재한다고 생각해보자. 가구의 디자인은 아주 자주 바뀐다. 그리고 당신은 가구점을 운영하면서 즉각 손님이 요구하는 가구를 가져다 줘야 한다.
각 개체가 필수적으로 가져야 할 특성 (소파, 의자, 커피테이블 등)은 인터페이스에 정의하고 가구의 디자인 타입을 지정한 하위 클래스를 생성한다. 이 하위 클래스는 부모 클래스의 함수를 오버라이드함으로서 해당 객체가 필요한 특징을 구현할 수 있다.
추가 공부가 필요한 부분: 인터페이스와 클래스의 차이
https://www.tutorialspoint.com/differences-between-interface-and-class-in-java
from abc import ABC, abstractmethod
class Browser(ABC):
"""
Creates "Abstract Product A"
"""
# Interface - Create Search Toolbar
@abstractmethod
def create_search_toolbar(self):
pass
# Interface - Create Browser Window
@abstractmethod
def create_browser_window(self):
pass
class Messenger(ABC):
"""
Creates "Abstract Product B"
"""
@abstractmethod
# Interface - Create Messenger Window
def create_messenger_window(self):
pass
class VanillaBrowser(Browser):
"""
Type: Concrete Product
Abstract methods of the Browser base class are implemented.
"""
# Interface - Create Search Toolbar
def create_search_toolbar(self):
print("Search Toolbar Created")
# Interface - Create Browser Window]
def create_browser_window(self):
print("Browser Window Created")
class VanillaMessenger(Messenger):
"""
Type: Concrete Product
Abstract methods of the Messenger base class are implemented.
"""
# Interface - Create Messenger Window
def create_messenger_window(self):
print("Messenger Window Created")
class SecureBrowser(Browser):
"""
Type: Concrete Product
Abstract methods of the Browser base class are implemented.
"""
# Abstract Method of the Browser base class
def create_search_toolbar(self):
print("Secure Browser - Search Toolbar Created")
# Abstract Method of the Browser base class
def create_browser_window(self):
print("Secure Browser - Browser Window Created")
def create_incognito_mode(self):
print("Secure Browser - Incognito Mode Created")
class SecureMessenger(Messenger):
"""
Type: Concrete Product
Abstract methods of the Messenger base class are implemented.
"""
# Abstract Method of the Messenger base class
def create_messenger_window(self):
print("Secure Messenger - Messenger Window Created")
def create_privacy_filter(self):
print("Secure Messenger - Privacy Filter Created")
def disappearing_messages(self):
print("Secure Messenger - Disappearing Messages Feature Enabled")
java에 abstract class가 있다. 자바의 추상 클래스는 이를 상속받은 다른 모든 객체들이 "반드시" 메소드를 오버라이딩해야한다.
여기서 잠깐, 오버라이딩과 오버로드의 차이점이 뭘까?
overriding : 부모 클래스에서 정의한 method를 자식 클래스에서 재정의하는 것을 말한다.
overloading: 두 메서드가 같은 이름을 가지고 있으나 인자의 수나 자료형이 다른 경우를 말한다.
// 오버로딩
public double computeArea(Circle c) { ... }
public double computeArea(Circle c1, Circle c2) { ... }
public double computeArea(Square c) { ... }
https://gmlwjd9405.github.io/2018/08/09/java-overloading-vs-overriding.html
// 오버라이딩
public abstract class Shape {
public void printMe() { System.out.println("Shape"); }
public abstract double computeArea();
}
public class Circle extends Shape {
private double rad = 5;
@Override // 개발자의 실수를 방지하기 위해 @Override(annotation) 쓰는 것을 권장
public void printMe() { System.out.println("Circle"); }
public double computeArea() { return rad * rad * 3.15; }
}
public class Ambiguous extends Shape {
private double area = 10;
public double computeArea() { return area; }
}
https://gmlwjd9405.github.io/2018/08/09/java-overloading-vs-overriding.html
추상 팩토리 패턴: https://gmlwjd9405.github.io/2018/08/08/abstract-factory-pattern.html
복잡한 클래스를 하나씩 정의할 수 있도록 해주는 클래스.
빌더 패턴은 복잡한 객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법을 제공한다.
빌더 패턴은 Builder라는 별도의 클래스를 만들어 필수 값에 대해서는 생성자를 통해, 선택적인 값에 대해서는 메소드를 통해 step-by-step으로 값을 입력받는다. 그리고 builder() method를 이용해 최종적으로 하나의 인스턴스를 만든다.
복잡한 여러 단계를 거쳐 집을 짓는다고 생각해보자. 집이라는 instance에는 수많은 옵션이 붙어있다. 창문의 개수, 수영장 여부, 정원 여부, 문의 재질, 창문 모양 등 엄청나게 많은 특성들이 부여된다. 이 모든 특성을 한 클래스로 정의하면 어떤 옵션이 꼭 필수적인 옵션인지, 어떤 옵션이 어떤 값들을 가질 수 있는지에 대해 눈에 들어오지 않을 것이다.
이런 경우 빌더 패턴을 사용하면 좋다. 복잡한 객체를 생성하는 클래스와 property를 지정하는 클래스를 분리해, 한 눈에 각종 property를 관리하기 쉽게 만드는 것이다.
아래 파이썬 예제 코드를 보자.
class Car:
def __init__(
self,
make,
model,
year,
color,
num_door,
engine_type,
transmission_type,
sunroof=False,
gps=False
):
self.make=make
self.model=model
self.year=year
self.color=color
self.num_door=num_door
self.engine_type=engine_type
self.transmission_type=transmission_type
self.sunroof=sunroof,
self.gps=gps
class CarBuilder:
def __init__(self):
self.make=None
self.model=None
self.year=None
self.color=None
self.num_door=None
self.engine_type=None
self.transmission_type=None
self.sunroof=None
self.gps =None
def set_make(self, make):
self.model=model
return self
def set_year(self, year):
self.year=year
return self
def set_color(self, year):
self.year=year
return self
def set_num_doors(self, num_doors):
self.num_doors=num_doors
return self
def set_engine_type(self, engine_type):
self.=engine_type
return self
def set_transmission_type(self, transmission_type):
self.=transmission_type
return self
def set_sunroof(self, sunroof):
self.sunroof=sunroof
return self
def set_gps(self, gps):
self.gps=gps
return self
def build(self):
return Car(
self.make,
self.model,
self.year,
self.color,
self.num_doors,
self.engine_type,
self.transmission_type,
sunroof=self.sunroof,
gps=self.gps
)
if __name__ == '__main__:
# 1안: builder 타입 없이 인스턴스를 생성하기
my_car = Car(
"Toyota",
"Camry",
2022,
"Blue",
4,
"Gasoline",
"Automatic",
sunroof=True,
gps=True
)
# 2안. builder 디자인 패턴대로 인스턴스를 생성하기
# 복잡한 오브젝트를 만들 때 도움이 된다.
builder = CarBuilder()
my_car=builder.set_make("Toyota") \
.set_model("Carmy") \
.set_year(2022) \
.set_color("Blue") \
.set_engine_type("Gasoline") \
.set_transmission_type("Automatic") \
.set_sunroof(True) \
.set_gps(True) \
.build()
오로지 단 하나의 객체만을 생성하는 클래스. 싱글턴 패턴을 이용하면 한 class가 늘 같은 메모리에 저장되는 동일한 instance만을 반환하기 때문에 instance의 유일성을 보장할 수 있다. 가령 자율주행차량의 차량 운행 정보를 저장하는 database라는 인스턴스가 필요하다고 가정하자. 이 인스턴스에는 차량의 속도, 방향이 저장되고 이 instance에 속도와 방향을 write하면 그 값대로 차량이 운행한다고 가정하자. 여러 센서들이 이 instance의 value를 읽어오는데, 이럴때마다 database를 생성하는 객체가 별도의 instance를 반환하면 제대로 운전을 할 수 없다. 따라서 이럴때 늘 같은 메모리에 저장되는 싱글턴 패턴이 적용된 클래스를 정의한다.
특징은 다음과 같다.
1. 공유하는 data에 대해 동시에 접속할 때
2. data에 접근하는 포인트를 하나만 만들어야 할 때
3. 한 프로그램이 실행되는 동안 한 class에서 단 하나의 instacne만을 만들어야 할 때.
class MongoManager:
__instance = None
@staticmethod
def getInstance():
if MongoManager.__instance == None:
MongoManager()
return MongoManager.__instance
def __init__(self):
if MongoManager.__instance != None:
raise Exception("This class is a singleton!")
else:
MongoManager.__instance = pymongo.MongoClient('localhost', 27017)
if __name__ == "__main__":
db = MongoManager.getInstance()
keywords: getInstance, protect method(in case of c++ or java)
https://refactoring.guru/design-patterns/catalog
abstract factory pattern 예제코드 참고:
https://stackabuse.com/abstract-factory-design-pattern-in-python/
빌더 클래스: https://4z7l.github.io/2021/01/19/design_pattern_builder.html
빌더 클래스 예제 코드 참고: https://www.linkedin.com/pulse/building-objects-easy-way-exploring-builder-pattern-adam-dejans-jr-
팩토리 메소드 예제 코드 참고: https://www.geeksforgeeks.org/factory-method-python-design-patterns/
Builder 클래스 설명 참고: https://readystory.tistory.com/121
싱글턴 패턴의 단점: https://blog.hexabrain.net/394#%EB%AC%B8%EC%A0%9C%EC%A0%90
싱글턴 패턴의 장점: https://velog.io/@yukina1418/OOP%EC%97%90%EC%84%9C-IoC%EC%99%80-DI%EA%B0%80-%EC%A4%91%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-%EC%9E%91%EC%84%B1%EC%A4%91#%EC%8B%B1%EA%B8%80%ED%86%A4%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80
몽고connection을 singleton 패턴으로 만들기: https://stackoverflow.com/questions/59137628/cant-grasp-making-a-simple-singleton-in-python-for-a-mongodb-manager