├── structural ├── decorator │ ├── test.txt │ ├── data.txt │ ├── main.py │ ├── test.py │ ├── README.md │ └── datasource.py ├── facade │ ├── main.py │ ├── test.py │ ├── README.md │ └── video.py ├── flyweight │ ├── test.py │ ├── main.py │ ├── README.md │ └── tree.py ├── adapter │ ├── main.py │ ├── README.md │ ├── test.py │ └── peg.py ├── bridge │ ├── main.py │ ├── device.py │ ├── README.md │ ├── test.py │ └── remote.py ├── proxy │ ├── main.py │ ├── test.py │ ├── README.md │ └── youtube.py └── composite │ ├── README.md │ ├── test.py │ ├── graphic.py │ └── main.py ├── .gitignore ├── pytest.sh ├── behavioral ├── command │ └── README.md ├── memento │ └── README.md ├── visitor │ └── README.md ├── iterator │ └── README.md ├── mediator │ └── README.md ├── observer │ └── README.md ├── template_method │ └── README.md ├── chain_of_responsibility │ └── README.md ├── state │ ├── main.py │ ├── test.py │ ├── player.py │ ├── README.md │ └── state.py └── strategy │ ├── strategy.py │ ├── main.py │ ├── test.py │ └── README.md ├── creational ├── singleton │ ├── test.py │ ├── README.md │ └── main.py ├── factory_method │ ├── main.py │ ├── test.py │ ├── button.py │ ├── dialog.py │ └── README.md ├── builder │ ├── engine.py │ ├── test.py │ ├── main.py │ ├── README.md │ └── builder.py ├── abstract_factory │ ├── button.py │ ├── checkbox.py │ ├── test.py │ ├── main.py │ ├── README.md │ └── factory.py ├── prototype │ ├── main.py │ ├── test.py │ ├── README.md │ └── prototype.py └── simple_factory │ ├── README.md │ ├── test.py │ └── main.py ├── .github └── workflows │ └── test.yml ├── requirements.txt └── README.md /structural/decorator/test.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 3 | 4 5 6 -------------------------------------------------------------------------------- /structural/decorator/data.txt: -------------------------------------------------------------------------------- 1 | eJwrCQ4zCqz0zffPdnLUd0m3BQAuHAUq -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | __pycache__/ 3 | .pytest_cache/ 4 | data.txt -------------------------------------------------------------------------------- /pytest.sh: -------------------------------------------------------------------------------- 1 | for f in */*/test.py ; do 2 | echo "Running tests in $f" 3 | pytest $f 4 | done -------------------------------------------------------------------------------- /behavioral/command/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/memento/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/visitor/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/iterator/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/mediator/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/observer/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/template_method/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /behavioral/chain_of_responsibility/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### . 6 | 7 | ### 적용 상황 8 | 9 | ### 장점 10 | 11 | ### 단점 12 | 13 | ### 타 패턴과의 관계 14 | -------------------------------------------------------------------------------- /structural/facade/main.py: -------------------------------------------------------------------------------- 1 | from video import VideoConverter 2 | 3 | if __name__ == "__main__": 4 | converter = VideoConverter() 5 | print(converter.convert("test.mp4", "mp4")) 6 | print(converter.convert("test.ogg", "ogg")) 7 | -------------------------------------------------------------------------------- /structural/facade/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from video import VideoConverter 3 | 4 | 5 | def test(): 6 | converter = VideoConverter() 7 | print(converter.convert("test.mp4", "mp4")) 8 | print(converter.convert("test.ogg", "ogg")) 9 | -------------------------------------------------------------------------------- /creational/singleton/test.py: -------------------------------------------------------------------------------- 1 | from main import MyClass 2 | 3 | 4 | class Test(object): 5 | def test(self): 6 | m1 = MyClass("m1") 7 | m2 = MyClass("m2") 8 | print(m1.name, m2.name) 9 | print(id(m1), id(m2)) 10 | 11 | assert id(m1) == id(m2) 12 | -------------------------------------------------------------------------------- /structural/flyweight/test.py: -------------------------------------------------------------------------------- 1 | from tree import Forest 2 | 3 | 4 | def test(): 5 | forest = Forest() 6 | forest.plant_tree(0, 0, "oak", "green", "smooth") 7 | forest.plant_tree(1, 1, "oak", "green", "smooth") 8 | forest.plant_tree(2, 2, "oak", "green", "smooth") 9 | 10 | canvas = "canvas" 11 | forest.draw(canvas) 12 | -------------------------------------------------------------------------------- /creational/factory_method/main.py: -------------------------------------------------------------------------------- 1 | from dialog import Dialog 2 | 3 | 4 | class Application(object): 5 | def __init__(self, os: str): 6 | self.dialog = Dialog.of(os=os) 7 | 8 | def run(self): 9 | self.dialog.render() 10 | 11 | 12 | if __name__ == "__main__": 13 | app = Application(os="web") 14 | app.run() 15 | -------------------------------------------------------------------------------- /structural/flyweight/main.py: -------------------------------------------------------------------------------- 1 | from tree import Forest 2 | 3 | if __name__ == "__main__": 4 | forest = Forest() 5 | forest.plant_tree(0, 0, "oak", "green", "smooth") 6 | forest.plant_tree(1, 1, "oak", "green", "smooth") 7 | forest.plant_tree(2, 2, "oak", "green", "smooth") 8 | 9 | canvas = "canvas" 10 | forest.draw(canvas) 11 | -------------------------------------------------------------------------------- /creational/builder/engine.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Engine(ABC): 5 | @abstractmethod 6 | def get_type(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class SUVEngine(Engine): 11 | def get_type(self): 12 | return "SUV" 13 | 14 | 15 | class SportEngine(Engine): 16 | def get_type(self): 17 | return "Sport" 18 | -------------------------------------------------------------------------------- /behavioral/state/main.py: -------------------------------------------------------------------------------- 1 | from player import Player 2 | from state import ReadyState 3 | 4 | if __name__ == "__main__": 5 | player = Player(ReadyState(None)) 6 | player.click_next() 7 | player.click_previous() 8 | player.click_lock() 9 | player.click_play() 10 | player.click_lock() 11 | player.click_play() 12 | player.click_next() 13 | player.click_previous() 14 | -------------------------------------------------------------------------------- /structural/decorator/main.py: -------------------------------------------------------------------------------- 1 | from datasource import FileDataSource, CompressionDecorator, EncryptionDecorator 2 | 3 | if __name__ == "__main__": 4 | data_source = FileDataSource("data.txt") 5 | data_source = CompressionDecorator(data_source) 6 | data_source = EncryptionDecorator(data_source) 7 | 8 | data_source.write_data("Hello World") 9 | print(data_source.read_data()) 10 | -------------------------------------------------------------------------------- /creational/abstract_factory/button.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Button(ABC): 5 | @abstractmethod 6 | def on_click(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class WindowsButton(Button): 11 | def on_click(self): 12 | print("windows clicked") 13 | 14 | 15 | class MacButton(Button): 16 | def on_click(self): 17 | print("mac clicked") 18 | -------------------------------------------------------------------------------- /creational/abstract_factory/checkbox.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Checkbox(ABC): 5 | @abstractmethod 6 | def check(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class WindowsCheckbox(Checkbox): 11 | def check(self): 12 | print("windows checked") 13 | 14 | 15 | class MacCheckbox(Checkbox): 16 | def check(self): 17 | print("mac checked") 18 | -------------------------------------------------------------------------------- /behavioral/strategy/strategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Strategy(ABC): 5 | @abstractmethod 6 | def execute(self, x: int, y: int): 7 | pass 8 | 9 | 10 | class StrategyAdd(Strategy): 11 | def execute(self, x: int, y: int): 12 | return x + y 13 | 14 | 15 | class StrategyMultiply(Strategy): 16 | def execute(self, x: int, y: int): 17 | return x * y 18 | -------------------------------------------------------------------------------- /structural/adapter/main.py: -------------------------------------------------------------------------------- 1 | from peg import RoundHole, RoundPeg, SquarePeg, SquarePegAdapter 2 | 3 | if __name__ == "__main__": 4 | round_hole = RoundHole(radius=5) 5 | round_peg = RoundPeg(radius=4) 6 | square_peg = SquarePeg(width=8) 7 | square_peg_adapter = SquarePegAdapter(square_peg=square_peg) 8 | 9 | print(round_hole.fits(peg=round_peg)) 10 | print(round_hole.fits(peg=square_peg_adapter)) 11 | -------------------------------------------------------------------------------- /creational/abstract_factory/test.py: -------------------------------------------------------------------------------- 1 | from main import Application 2 | import pytest 3 | 4 | 5 | class Test(object): 6 | def test_windows(self): 7 | app = Application("windows") 8 | app.run() 9 | 10 | def test_mac(self): 11 | app = Application("mac") 12 | app.run() 13 | 14 | def test_other(self): 15 | with pytest.raises(KeyError): 16 | app = Application("other") 17 | app.run() 18 | -------------------------------------------------------------------------------- /creational/factory_method/test.py: -------------------------------------------------------------------------------- 1 | from main import Application 2 | import pytest 3 | 4 | 5 | class Test(object): 6 | def test_windows(self): 7 | app = Application("windows") 8 | app.run() 9 | 10 | def test_web(self): 11 | app = Application("web") 12 | app.run() 13 | 14 | def test_other(self): 15 | with pytest.raises(KeyError): 16 | app = Application("other") 17 | app.run() 18 | -------------------------------------------------------------------------------- /creational/prototype/main.py: -------------------------------------------------------------------------------- 1 | from prototype import Prototype, ConcretePrototype, SubClassPrototype 2 | 3 | if __name__ == "__main__": 4 | p1 = ConcretePrototype("p1") 5 | p2 = p1.clone() 6 | print("p1:", p1) 7 | print("p2:", p2) 8 | print("Is it same?", p1 == p2, "\n") 9 | 10 | p3 = SubClassPrototype("p3", 3) 11 | p4 = p3.clone() 12 | print("p3:", p3) 13 | print("p4:", p4) 14 | print("Is it same?", p3 == p4, "\n") 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: PyTest 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Check out repository code 11 | uses: actions/checkout@v2 12 | 13 | - name: Setup Python 14 | uses: actions/setup-python@v1 15 | 16 | - name: Install Dependencies 17 | run: pip install -r requirements.txt 18 | 19 | - name: Run Tests 20 | run: sh pytest.sh 21 | -------------------------------------------------------------------------------- /behavioral/strategy/main.py: -------------------------------------------------------------------------------- 1 | from strategy import Strategy, StrategyAdd, StrategyMultiply 2 | 3 | 4 | class Client(object): 5 | def __init__(self, strategy: Strategy): 6 | self.strategy = strategy 7 | 8 | def run(self, x, y): 9 | return self.strategy.execute(x, y) 10 | 11 | 12 | if __name__ == "__main__": 13 | strategy: Strategy = StrategyAdd() 14 | client = Client(strategy) 15 | ans = client.run(10, 10) 16 | print(ans) 17 | -------------------------------------------------------------------------------- /behavioral/strategy/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from main import Client 4 | from strategy import Strategy, StrategyAdd, StrategyMultiply 5 | 6 | 7 | def test_add(): 8 | strategy: Strategy = StrategyAdd() 9 | client = Client(strategy) 10 | ans = client.run(10, 10) 11 | assert ans == 20 12 | 13 | 14 | def test_multiply(): 15 | strategy: Strategy = StrategyMultiply() 16 | client = Client(strategy) 17 | ans = client.run(10, 10) 18 | assert ans == 100 19 | -------------------------------------------------------------------------------- /creational/abstract_factory/main.py: -------------------------------------------------------------------------------- 1 | from factory import GUIFactory 2 | 3 | 4 | class Application(object): 5 | def __init__(self, os: str): 6 | self.factory = GUIFactory.of(os=os) 7 | 8 | def run(self): 9 | button = self.factory.createButton() 10 | checkbox = self.factory.createCheckbox() 11 | 12 | button.on_click() 13 | checkbox.check() 14 | 15 | 16 | if __name__ == "__main__": 17 | app = Application(os="windows") 18 | app.run() 19 | -------------------------------------------------------------------------------- /creational/singleton/README.md: -------------------------------------------------------------------------------- 1 | # Singleton Pattern 2 | 3 | https://refactoring.guru/design-patterns/singleton 4 | 5 | > ### 어떤 클래스에 대해 단 하나의 객체만 생성되는 것을 보장 6 | 7 | - 데이터베이스나 파일처럼 공유된 자원에 대한 접근을 컨트롤하기 위해 사용 8 | - 새로운 객체 생성 시도 시 기존 생성된 객체를 반환함 9 | - 클라이언트는 요청한 객체가 전부 같은 객체임을 모를 수도 있음 10 | 11 | ### 적용 상황 12 | 13 | - 모든 클라이언트에 대해 단 하나의 인스턴스만 존재해야 할 때 14 | - 전역 변수 컨트롤을 위해 한 인스턴스만 필요할 때 15 | 16 | ### 장점 17 | 18 | - 단일 인스턴스 생성 보장 19 | 20 | ### 단점 21 | 22 | - 싱글톤 패턴 테스트하기 어려움 23 | - 의존성 문제가 가려지기 쉬움 24 | -------------------------------------------------------------------------------- /structural/adapter/README.md: -------------------------------------------------------------------------------- 1 | # Adapter Pattern 2 | 3 | https://refactoring.guru/design-patterns/adapter 4 | 5 | > ### 호환되지 않는 인터페이스와 호환되도록 6 | 7 | - adapter로 객체를 감싸고 외부에서는 wrapping 여부를 모르게 8 | - adapter를 이용해 서비스의 specific한 메소드를 자유롭게 클라이언트가 호출할 수 있게 함 9 | 10 | ### 적용 상황 11 | 12 | - 이미 존재하는 클래스가 인터페이스와 호환이 되지 않을 때 13 | - 부모클래스에 추가할 수 없는 자식클래스의 메소드를 재사용하고 싶을 때 14 | 15 | ### 장점 16 | 17 | - SRP: 프로그램의 기본 비즈니스 로직과 인터페이스 분리 18 | - OCP: 기존 클라이언트 코드를 수정하지 않고 어댑터만 수정하여 적용 가능 19 | 20 | ### 단점 21 | 22 | - 복잡성 증가 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==21.4.0 2 | black==21.12b0 3 | certifi==2021.10.8 4 | charset-normalizer==2.0.12 5 | click==8.0.3 6 | idna==3.3 7 | iniconfig==1.1.1 8 | mypy-extensions==0.4.3 9 | Naked==0.1.31 10 | packaging==21.3 11 | pathspec==0.9.0 12 | platformdirs==2.4.1 13 | pluggy==1.0.0 14 | py==1.11.0 15 | pycryptodome==3.4.3 16 | pyparsing==3.0.6 17 | pytest==6.2.5 18 | PyYAML==6.0 19 | requests==2.27.1 20 | shellescape==3.8.1 21 | toml==0.10.2 22 | tomli==1.2.3 23 | typing_extensions==4.0.1 24 | urllib3==1.26.9 25 | -------------------------------------------------------------------------------- /creational/singleton/main.py: -------------------------------------------------------------------------------- 1 | class Singleton(object): 2 | _instance = None 3 | 4 | def __new__(class_, *args, **kwargs): 5 | if not isinstance(class_._instance, class_): 6 | class_._instance = object.__new__(class_) 7 | return class_._instance 8 | 9 | 10 | class MyClass(Singleton): 11 | def __init__(self, name: str): 12 | self.name = name 13 | 14 | 15 | if __name__ == "__main__": 16 | m1 = MyClass("m1") 17 | m2 = MyClass("m2") 18 | print(m1.name, m2.name) 19 | print(id(m1), id(m2)) 20 | -------------------------------------------------------------------------------- /creational/prototype/test.py: -------------------------------------------------------------------------------- 1 | from prototype import Prototype, ConcretePrototype, SubClassPrototype 2 | 3 | 4 | class Test(object): 5 | def test_concrete(self): 6 | p1 = ConcretePrototype("p1") 7 | p2 = p1.clone() 8 | print("p1:", p1) 9 | print("p2:", p2) 10 | print("Is it same?", p1 == p2, "\n") 11 | 12 | def tet_subclass(self): 13 | p3 = SubClassPrototype("p3", 3) 14 | p4 = p3.clone() 15 | print("p3:", p3) 16 | print("p4:", p4) 17 | print("Is it same?", p3 == p4, "\n") 18 | -------------------------------------------------------------------------------- /creational/factory_method/button.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Button(ABC): 5 | @abstractmethod 6 | def on_click(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class WindowsButton(Button): 11 | def on_click(self): 12 | print("windows clicked") 13 | 14 | def delete(self): 15 | print("delete button") 16 | 17 | 18 | class HTMLButton(Button): 19 | def on_click(self): 20 | self._hover() 21 | print("html clicked") 22 | 23 | def _hover(self): 24 | print("hover") 25 | -------------------------------------------------------------------------------- /creational/prototype/README.md: -------------------------------------------------------------------------------- 1 | # Prototype Pattern 2 | 3 | https://refactoring.guru/design-patterns/prototype 4 | 5 | > ### 코드에 대한 의존성 없이 존재하는 객체를 그대로 복사 6 | 7 | - 모든 클래스에 대해 clone 메소드를 전부 구현 8 | - 어떤 클래스로 생성된 객체여도 clone 메소드를 통해 복사할 수 있도록 함. 9 | 10 | ### 적용 상황 11 | 12 | - concrete 클래스의 구현 방식과 의존성 없이 복사하고 싶을 때 13 | - configuration 을 토대로 복잡하게 생성하지 않고 기존에 있던 객체를 복사하여 편하게 사용하고 싶을 때 14 | 15 | ### 장점 16 | 17 | - concrete 클래스와 상관없이 복사 가능 18 | - 반복적인 객체 초기화 대신 pre-built 된 프로로타입 복사를 이용하여 편하게 객체 생성 가능 19 | - 복잡한 객체를 편하게 생성 20 | 21 | ### 단점 22 | 23 | - 순환 참조가 있는 복잡한 객체 복사는 어려움 24 | -------------------------------------------------------------------------------- /structural/bridge/main.py: -------------------------------------------------------------------------------- 1 | from device import TV, Radio 2 | from remote import Remote, AdvancedRemote 3 | 4 | if __name__ == "__main__": 5 | tv = TV() 6 | remote = Remote(tv) 7 | remote.toggle_power() 8 | remote.volume_up() 9 | remote.volume_down() 10 | remote.channel_up() 11 | remote.channel_down() 12 | 13 | radio = Radio() 14 | remote = AdvancedRemote(radio) 15 | remote.toggle_power() 16 | remote.volume_up() 17 | remote.volume_down() 18 | remote.channel_up() 19 | remote.channel_down() 20 | remote.mute() 21 | remote.volume_up() 22 | -------------------------------------------------------------------------------- /creational/simple_factory/README.md: -------------------------------------------------------------------------------- 1 | # Factory Pattern 2 | 3 | https://refactoring.guru/design-patterns/factory-comparison 4 | 5 | > ### object 생성 6 | 7 | ## factory 8 | 9 | - 클래스, 메소드, 함수 등 무언가를 생성하는 모든 것을 factory라 함 10 | - 일반적으로는 객체를 생성함. 11 | - 굉장히 넓은 범위의 단어로 포함하고 있는 의미가 많다. 12 | 13 | ## Creation Method 14 | 15 | - 객체를 생성하는 모든 메소드 16 | 17 | ## Static Creation Method 18 | 19 | - 클래스에서 정적으로 객체를 생성하는 메소드 20 | - 다양한 목적을 갖는 여러 생성자가 필요한 경우, 객체 재사용이 필요한 경우 사용 21 | 22 | ## Simple Factory 23 | 24 | - factory method 패턴, abstract factory 패턴의 기초. 25 | - 단일 클래스의 단일 메소드로 구현됨. 26 | - 파라미터를 통해 생성 객체가 정해짐. 27 | -------------------------------------------------------------------------------- /behavioral/state/test.py: -------------------------------------------------------------------------------- 1 | from player import Player 2 | from state import ReadyState, PlayingState, LockedState 3 | 4 | 5 | def test(): 6 | player = Player(ReadyState(None)) 7 | assert player.state.__class__.__name__ == "ReadyState" 8 | 9 | player.click_next() 10 | player.click_previous() 11 | player.click_lock() 12 | assert player.state.__class__.__name__ == "LockedState" 13 | 14 | player.click_play() 15 | player.click_lock() 16 | player.click_play() 17 | assert player.state.__class__.__name__ == "PlayingState" 18 | 19 | player.click_next() 20 | player.click_previous() 21 | -------------------------------------------------------------------------------- /structural/proxy/main.py: -------------------------------------------------------------------------------- 1 | from youtube import CachedYoutube, ThirdPartyYoutube 2 | 3 | if __name__ == "__main__": 4 | youtube = CachedYoutube(ThirdPartyYoutube()) 5 | youtube.download_video(1) 6 | youtube.download_video(2) 7 | youtube.download_video(3) 8 | youtube.list_videos() 9 | youtube.download_video(3) 10 | youtube.download_video(4) 11 | 12 | print("-" * 20) 13 | youtube = ThirdPartyYoutube() 14 | youtube.download_video(1) 15 | youtube.download_video(2) 16 | youtube.download_video(3) 17 | youtube.list_videos() 18 | youtube.download_video(3) 19 | youtube.download_video(4) 20 | -------------------------------------------------------------------------------- /creational/simple_factory/test.py: -------------------------------------------------------------------------------- 1 | from main import UserFactory 2 | import pytest 3 | 4 | 5 | TYPE_CONST = "[+] type:" 6 | 7 | 8 | class Test(object): 9 | def test_admin(self): 10 | user = UserFactory.create_user("admin") 11 | print(TYPE_CONST, user.get_type()) 12 | user.action() 13 | 14 | def test_normal(self): 15 | user = UserFactory.create_user("normal") 16 | print(TYPE_CONST, user.get_type()) 17 | user.action() 18 | 19 | def test_other(self): 20 | with pytest.raises(KeyError): 21 | user = UserFactory.create_user("other") 22 | print(TYPE_CONST, user.get_type()) 23 | user.action() 24 | -------------------------------------------------------------------------------- /structural/proxy/test.py: -------------------------------------------------------------------------------- 1 | from youtube import CachedYoutube, ThirdPartyYoutube 2 | 3 | 4 | def test_cached_youtube(): 5 | youtube = CachedYoutube(ThirdPartyYoutube()) 6 | youtube.download_video(1) 7 | youtube.download_video(2) 8 | youtube.download_video(3) 9 | youtube.list_videos() 10 | youtube.download_video(3) 11 | youtube.download_video(4) 12 | 13 | assert youtube.get_download_count() == 4 14 | 15 | 16 | def test_third_party_youtube(): 17 | youtube = ThirdPartyYoutube() 18 | youtube.download_video(1) 19 | youtube.download_video(2) 20 | youtube.download_video(3) 21 | youtube.list_videos() 22 | youtube.download_video(3) 23 | youtube.download_video(4) 24 | 25 | assert youtube.get_download_count() == 5 26 | -------------------------------------------------------------------------------- /structural/composite/README.md: -------------------------------------------------------------------------------- 1 | # Composite Pattern 2 | 3 | https://refactoring.guru/design-patterns/composite 4 | 5 | > ### 리눅스폴더구조처럼 객체 간의 트리구조 표현 시 사용 6 | 7 | - 리눅스 폴더 구조, 디자인 툴의 컴포넌트들, 회사 계급 체계과 같은 tree structure 를 표현할 때 사용함 8 | - 각 class들이 모두가 똑같은 인터페이스를 구현하고 instance는 재귀적으로 그 안에 포함된 instance의 메소드들을 콜하고 통합함 9 | - component라는 interface가 존재하고 리프 노드가 실제로 이를 실행하는 역할을 함. 그 사이의 모든 노드들은 composite라는 노드로 이뤄지며 component라는 타입의 원소들을 포함함. 리프일 수도 있고 같은 composite일 수도 있음. 10 | - leaf와 composite이 거의 유사한 역할을 할 때 사용하는 것을 추천. 11 | 12 | ### 적용 상황 13 | 14 | - 트리 같은 객체 구조 표현 15 | - 클라이언트가 단순한 원소와 복잡한 원소를 똑같이 처리하게 하고 싶을 때 16 | 17 | ### 장점 18 | 19 | - 복잡한 트리구조를 사용하기 쉽게 할 수 있다. 다형성과 재귀를 사용함. 20 | - OCP: 기존의 코드를 깨지 않고 새로운 타입을 추가할 수 있음. 21 | 22 | ### 단점 23 | 24 | - composite와 leaf의 기능이 너무 다를 때 같은 인터페이스로 구현하는 것이 어려움 25 | -------------------------------------------------------------------------------- /behavioral/state/player.py: -------------------------------------------------------------------------------- 1 | class Player(object): 2 | def __init__(self, state): 3 | self.state = state 4 | self.state.player = self 5 | 6 | def change_state(self, state): 7 | print( 8 | f"Change state: {self.state.__class__.__name__} -> {state.__class__.__name__}" 9 | ) 10 | self.state = state 11 | 12 | def click_lock(self): 13 | print("Click the lock button") 14 | self.state.click_lock() 15 | 16 | def click_play(self): 17 | print("Click the play button") 18 | self.state.click_play() 19 | 20 | def click_next(self): 21 | print("Click the next button") 22 | self.state.click_next() 23 | 24 | def click_previous(self): 25 | print("Click the previous button") 26 | self.state.click_previous() 27 | -------------------------------------------------------------------------------- /structural/bridge/device.py: -------------------------------------------------------------------------------- 1 | class Device(object): 2 | def __init__(self): 3 | self._is_on = False 4 | self._volume = 0 5 | self._channel = 0 6 | 7 | def is_enabled(self): 8 | return self._is_on 9 | 10 | def enable(self): 11 | self._is_on = True 12 | 13 | def disable(self): 14 | self._is_on = False 15 | 16 | @property 17 | def volume(self): 18 | return self._volume 19 | 20 | @volume.setter 21 | def volume(self, volume): 22 | self._volume = volume 23 | 24 | @property 25 | def channel(self): 26 | return self._channel 27 | 28 | @channel.setter 29 | def channel(self, channel): 30 | self._channel = channel 31 | 32 | 33 | class TV(Device): 34 | pass 35 | 36 | 37 | class Radio(Device): 38 | pass 39 | -------------------------------------------------------------------------------- /structural/adapter/test.py: -------------------------------------------------------------------------------- 1 | from peg import RoundHole, RoundPeg, SquarePeg, SquarePegAdapter 2 | 3 | 4 | class Test(object): 5 | def test_small(self): 6 | round_hole = RoundHole(radius=5) 7 | round_peg = RoundPeg(radius=4) 8 | square_peg = SquarePeg(width=4) 9 | square_peg_adapter = SquarePegAdapter(square_peg=square_peg) 10 | 11 | assert round_hole.fits(peg=round_peg) == True 12 | assert round_hole.fits(peg=square_peg_adapter) == True 13 | 14 | def test_large(self): 15 | round_hole = RoundHole(radius=5) 16 | round_peg = RoundPeg(radius=4) 17 | square_peg = SquarePeg(width=8) 18 | square_peg_adapter = SquarePegAdapter(square_peg=square_peg) 19 | 20 | assert round_hole.fits(peg=round_peg) == True 21 | assert round_hole.fits(peg=square_peg_adapter) == False 22 | -------------------------------------------------------------------------------- /structural/facade/README.md: -------------------------------------------------------------------------------- 1 | # Facade Pattern 2 | 3 | https://refactoring.guru/design-patterns/facade 4 | 5 | > ### 복잡한 클래스의 단순화된 인터페이스 제공 6 | 7 | - 복잡한 서브시스템을 몰라도 클라이언트가 사용하기 쉽게 함 8 | - 자유도를 낮춤으로써 사용성을 높임 9 | 10 | ### 적용 상황 11 | 12 | - 복잡한 서브시스템에서 제한적이나 사용하기 쉬운 인터페이스를 제공하고 싶을 때 13 | - 서브시스템을 레이어로 구조화하고 싶을 때 (여러 서브시스템간의 결합도를 낮출 수 있음) 14 | 15 | ### 장점 16 | 17 | - 서브시스템의 복잡도와 사용 난이도를 분리할 수 있음 18 | 19 | ### 단점 20 | 21 | - facade는 God Object(모든 것을 알고 있는 객체로 SRP를 위반함)가 될 수 있음 22 | 23 | ### 타 패턴과의 관계 24 | 25 | - Facade는 존재하는 객체를 위해 새로운 인터페이스 생성을 하나 Adapter는 기존의 인터페이스를 사용가능하게 변경 26 | - Adapter는 한 객체만 wrapping, Facade는 모든 서브시스템을 wrapping함 27 | - Facade에서 서브시스템 객체 생성 방법만 가리고 싶을 경우 Abstract Factory 사용하면 됨 28 | - Flyweight는 엄청나게 많은 작은 객체를 표현한다면, Facade는 엄청나게 큰 서브시스템을 단 하나의 객체로 표현함 29 | - TODO: Mediator 30 | - Facade 객체는 보통 하나로 충분하므로 싱글톤으로도 많이 사용함 31 | - TODO: Proxy 32 | -------------------------------------------------------------------------------- /structural/bridge/README.md: -------------------------------------------------------------------------------- 1 | # Bridge Pattern 2 | 3 | https://refactoring.guru/design-patterns/bridge 4 | 5 | > ### 2가지 클래스들의 집합을 연결 6 | 7 | - 독립적인 차원의 집합들이 존재할 때 n x m 클래스를 모두 구현할 수 없음 8 | - 한 클래스를 구현할 때 여러 dim이 필요한 경우 차원 하나를 별도의 클래스 계층으로 추출 9 | - implementation 은 interface + concrete implementation, abstraction 은 부모 클래스, 자식 클래스로 구성 10 | - implementation, abstraction 은 항상 호환 가능하며 이를 통해 상속받는 클래스들 또한 호환 보장 11 | - abstraction 에서 implementation local variable로 갖고 있음 12 | 13 | ### 적용 상황 14 | 15 | - 여러 기능이 있는 모놀리틱 클래스를 나누고 싶을 때 16 | - 독립적인 차원의 클래스로 확장이 필요할 때 17 | - 런타임에서 implementation을 변경해야 할 때 18 | 19 | ### 장점 20 | 21 | - platform-independent한 클래스와 앱을 생성할 수 있음 22 | - 클라이언트 코드는 하이레벨 abstraction 코드와 동작함. 디테일을 몰라도 됨. 23 | - OCP: abstraction, implementation 서로 독립적으로 개발 가능 24 | - SRP: high-level logic은 abstraction에서, 디테일은 implementation에서 25 | 26 | ### 단점 27 | 28 | - 복잡성 증가 29 | -------------------------------------------------------------------------------- /structural/adapter/peg.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class RoundHole(object): 5 | def __init__(self, radius: float): 6 | self.radius = radius 7 | 8 | def fits(self, peg: object) -> bool: 9 | return self.radius >= peg.get_radius() 10 | 11 | 12 | class RoundPeg(object): 13 | def __init__(self, radius: float): 14 | self.radius = radius 15 | 16 | def get_radius(self) -> float: 17 | return self.radius 18 | 19 | 20 | class SquarePeg(object): 21 | def __init__(self, width: float): 22 | self.width = width 23 | 24 | def get_width(self) -> float: 25 | return self.width 26 | 27 | 28 | class SquarePegAdapter(RoundPeg): 29 | def __init__(self, square_peg: SquarePeg): 30 | self.square_peg = square_peg 31 | 32 | def get_radius(self) -> float: 33 | return self.square_peg.get_width() * math.sqrt(2) / 2 34 | -------------------------------------------------------------------------------- /structural/flyweight/README.md: -------------------------------------------------------------------------------- 1 | # Flyweight Pattern 2 | 3 | https://refactoring.guru/design-patterns/flyweight 4 | 5 | > ### 여러 객체 간의 공유 메모리 적용 6 | 7 | - 여러 객체들 간의 공유 정보들을 캐싱하여 메모리 절약함 8 | - 게임처럼 사양이 중요한 곳에서 많이 사용됨 9 | - 객체의 고유 특성(intrinsic state)은 객체에 저장하고 외부 환경에 따라 변하는 특성(extrinsic state)은 따로 저장함 10 | - Context(모든 상태 포함)클래스에서 객체의 고유 특성(공유 가능한 부분)들만 Flyweight클래스로 저장함. 11 | - Flyweight 객체는 Flyweight Factory에서 캐싱됨. 기존에 있으면 찾아주고 없으면 추가. 12 | 13 | ### 적용 상황 14 | 15 | - 정해진 RAM을 훨씬 넘는 객체를 지원해야 하는 경우에 사용 16 | - Flyweight 객체의 intrinsic state는 변경되어서는 안됨 17 | 18 | ### 장점 19 | 20 | - RAM 절약 21 | 22 | ### 단점 23 | 24 | - RAM 을 절약하는 대신 CPU가 많이 사용됨 25 | 26 | ### 타 패턴과의 관계 27 | 28 | - Composite 패턴에서 메모리 절약하기 위해 Flyweight 사용함 29 | - Flyweight는 엄청나게 많은 작은 객체를 표현한다면, Facade는 엄청나게 큰 서브시스템을 단 하나의 객체로 표현함 30 | - Flyweight는 Singleton과 유사함. 31 | - 첫번째 차이점. 싱글톤은 하나의 객체만을 Flyweight는 여러 intrinsic state 가질 수 있음 32 | - 두번째 차이점. 싱글톤은 수정가능. Flyweight은 수정불가능. 33 | -------------------------------------------------------------------------------- /creational/factory_method/dialog.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from button import WindowsButton, HTMLButton 4 | 5 | 6 | class Dialog(ABC): 7 | @classmethod 8 | def of(cls, os: str): 9 | if os == "windows": 10 | return WindowsDialog() 11 | elif os == "web": 12 | return WebDialog() 13 | else: 14 | raise KeyError("unknown os") 15 | 16 | @abstractmethod 17 | def render(self): 18 | raise NotImplementedError() 19 | 20 | 21 | class WindowsDialog(object): 22 | def __init__(self): 23 | self.button = WindowsButton() 24 | 25 | def render(self): 26 | print("turn on windows") 27 | self.button.on_click() 28 | self.button.delete() 29 | 30 | 31 | class WebDialog(object): 32 | def __init__(self): 33 | self.button = HTMLButton() 34 | 35 | def render(self): 36 | print("turn on web") 37 | self.button.on_click() 38 | -------------------------------------------------------------------------------- /structural/composite/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from main import ImageEditor 4 | 5 | 6 | @pytest.fixture 7 | def editor(): 8 | editor = ImageEditor() 9 | dot1 = editor.add_dot(10, 10) 10 | dot2 = editor.add_dot(5, 2) 11 | circle = editor.add_circle(20, 20, 10) 12 | return editor, dot1, dot2, circle 13 | 14 | 15 | def test_draw(editor): 16 | editor, dot1, dot2, circle = editor 17 | ans = editor.draw() 18 | 19 | assert ans == "10, 10: .\n5, 2: .\n20, 20: o" 20 | 21 | 22 | def test_move(editor): 23 | editor, dot1, dot2, circle = editor 24 | editor.move(0, 0, dot1) 25 | ans = editor.draw() 26 | 27 | assert ans == "0, 0: .\n5, 2: .\n20, 20: o" 28 | 29 | 30 | def test_group(editor): 31 | editor, dot1, dot2, circle = editor 32 | editor.move(0, 0, dot1) 33 | group = editor.group_selected(dot1, circle) 34 | editor.move(-5, -5, group) 35 | ans = editor.draw() 36 | 37 | assert ans == "5, 2: .\n-5, -5: .\n-5, -5: o" 38 | -------------------------------------------------------------------------------- /structural/bridge/test.py: -------------------------------------------------------------------------------- 1 | from device import TV, Radio, Device 2 | from remote import Remote, AdvancedRemote 3 | 4 | 5 | class Test(object): 6 | def test_tv_volume(self): 7 | tv: Device = TV() 8 | remote = Remote(tv) 9 | remote.toggle_power() 10 | remote.volume_up() 11 | assert tv.volume == 1 12 | remote.volume_down() 13 | assert tv.volume == 0 14 | 15 | def test_tv_channel(self): 16 | tv: Device = TV() 17 | remote = Remote(tv) 18 | remote.toggle_power() 19 | remote.volume_up() 20 | assert tv.volume == 1 21 | remote.volume_down() 22 | assert tv.volume == 0 23 | 24 | def test_mute(self): 25 | radio: Device = Radio() 26 | remote = AdvancedRemote(radio) 27 | remote.toggle_power() 28 | remote.volume_up() 29 | assert radio.volume == 1 30 | remote.volume_down() 31 | assert radio.volume == 0 32 | remote.mute() 33 | assert radio.volume == 0 34 | -------------------------------------------------------------------------------- /creational/factory_method/README.md: -------------------------------------------------------------------------------- 1 | # Factory Method Pattern 2 | 3 | https://refactoring.guru/design-patterns/factory-method 4 | 5 | > ### object 생성을 위한 interface 제공 6 | 7 | - 모든 생성 디자인 패턴의 시작. 8 | - OOP의 기본이자 코어인 상속에 대한 핵심 디자인 패턴. 9 | - SRP를 위해 object 생성과 object 사용 부분을 분리. 10 | - Product Interface 는 필수이나 Creator Interface 는 때에 따라 다르다. 11 | 1. Product 외부에서 사용해야 하는 메서드가 전부 같은 경우 Creator Interface 필요 X. 12 | 2. Product 외부에서 사용해야 하는 메서드가 다른 경우 Creator Interface 정의 후 Product에 dependent한 Concrete Creator class 구현 필요. 13 | - Creator Interface 가 반드시 abstract class 일 필요는 없다. 모든 Concrete Creator Class 에서 공통적으로 필요한 메서드는 Interface 에서 구현한다. 14 | 15 | ### 적용 상황 16 | 17 | - 사용해야 하는 객체의 정확한 타입과 종속성을 모를 때 18 | - 개발한 컴포넌트의 확장을 라이브러리 사용자에게 추천할 때 19 | - 존재하는 객체를 재사용함으로서 시스템 자원을 절약하고 싶을 때 20 | 21 | ### 장점 22 | 23 | - creator 와 concrete product class 의 decoupling 24 | - product creation responsibilty 와 product using responsibility 분리 (SRP) 25 | - new type product 쉽게 추가 가능 (OCP) 26 | 27 | ### 단점 28 | 29 | - 새로운 하위 클래스가 추가될 경우 클래스가 엄청 많아질 수 있음 30 | -------------------------------------------------------------------------------- /creational/prototype/prototype.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Prototype(ABC): 5 | @abstractmethod 6 | def clone(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class ConcretePrototype(Prototype): 11 | def __init__(self, name: str): 12 | self.name = name 13 | 14 | def clone(self): 15 | return ConcretePrototype(self.name) 16 | 17 | def __str__(self) -> str: 18 | return "name: {}".format(self.name) 19 | 20 | def __eq__(self, o) -> bool: 21 | return self.name == o.name 22 | 23 | 24 | class SubClassPrototype(ConcretePrototype): 25 | def __init__(self, name: str, age: int): 26 | super().__init__(name) 27 | self.age = age 28 | 29 | def clone(self): 30 | return SubClassPrototype(self.name, self.age) 31 | 32 | def __str__(self) -> str: 33 | return "name: {}, age: {}".format(self.name, self.age) 34 | 35 | def __eq__(self, o) -> bool: 36 | return self.name == o.name and self.age == o.age 37 | -------------------------------------------------------------------------------- /creational/builder/test.py: -------------------------------------------------------------------------------- 1 | from main import Director 2 | from builder import Builder, CarBuilder, CarManualBuilder, Car, Manual 3 | 4 | 5 | class Test(object): 6 | def test_sports_car(self): 7 | director: Director = Director() 8 | builder: Builder = CarBuilder() 9 | director.make_sports_car(builder) 10 | car: Car = builder.getResult() 11 | 12 | builder: Builder = CarManualBuilder() 13 | director.make_sports_car(builder) 14 | manual: Manual = builder.getResult() 15 | 16 | print("Car >>>\n", car) 17 | print("Manual >>>\n", manual) 18 | 19 | def test_suv(self): 20 | director: Director = Director() 21 | builder: Builder = CarBuilder() 22 | director.make_suv(builder) 23 | car: Car = builder.getResult() 24 | 25 | builder: Builder = CarManualBuilder() 26 | director.make_suv(builder) 27 | manual: Manual = builder.getResult() 28 | 29 | print("Car >>>\n", car) 30 | print("Manual >>>\n", manual) 31 | -------------------------------------------------------------------------------- /creational/builder/main.py: -------------------------------------------------------------------------------- 1 | from builder import Builder, CarBuilder, CarManualBuilder, Car, Manual 2 | from engine import SUVEngine, SportEngine 3 | 4 | 5 | class Director(object): 6 | def make_suv(self, builder: Builder): 7 | builder.reset() 8 | builder.setSeats(4) 9 | builder.setEngine(SUVEngine()) 10 | builder.setTripComputer() 11 | builder.setGPS() 12 | 13 | def make_sports_car(self, builder: Builder): 14 | builder.reset() 15 | builder.setSeats(2) 16 | builder.setEngine(SportEngine()) 17 | builder.setTripComputer() 18 | builder.setGPS() 19 | 20 | 21 | if __name__ == "__main__": 22 | director: Director = Director() 23 | builder: Builder = CarBuilder() 24 | director.make_sports_car(builder) 25 | car: Car = builder.getResult() 26 | 27 | builder: Builder = CarManualBuilder() 28 | director.make_sports_car(builder) 29 | manual: Manual = builder.getResult() 30 | 31 | print("Car >>>\n", car) 32 | print("Manual >>>\n", manual) 33 | -------------------------------------------------------------------------------- /creational/abstract_factory/README.md: -------------------------------------------------------------------------------- 1 | # Abstract Factory Pattern 2 | 3 | https://refactoring.guru/design-patterns/abstract-factory 4 | 5 | > ### 관련된 objects의 집단 생성을 위한 inteface 제공 6 | 7 | - 총 4가지의 파트로 구성됨. 8 | 9 | 1. Abstract Products 10 | - product 의 추상화 클래스 11 | 2. Concrete Products 12 | - abstract product class를 상속받는 클래스 13 | 3. Abstract Factory 14 | - abstract product 객체를 생성하는 추상 팩토리 클래스 15 | 4. Concrete Factories 16 | - abstract factory class를 상속받아 concrete product 객체를 생성하는 팩토리 클래스 17 | 18 | - Client 는 abstract factory class를 사용하며 내부 구현 방식과는 무관하게 동작가능하도록 함 19 | - 객체간의 호환성이 항상 보장됨. (mac button 과 window checkbox가 동시에 생성되지 않음) 20 | - builder 패턴은 복잡한 객체들을 생성하는데 집중하나 abstract factory 패턴은 관련된 객체간의 생성에 특화 21 | 22 | ### 적용 상황 23 | 24 | - 공존가능한 객체 집단이 확실하게 분리되며 객체 집단 생성에 대해 의존성을 갖고 싶지 않을 때 25 | 26 | ### 장점 27 | 28 | - 생성되는 여러 객체 간의 호환성이 보장됨 29 | - concrete product class와 client 코드의 decoupling 30 | - product creation code를 한 곳에서만 사용 (SRP) 31 | - new type product 쉽게 추가 가능 (OCP) 32 | 33 | ### 단점 34 | 35 | - 새로운 인터페이스와 클래스가 많이 생성되어 코드 구조가 복잡해질 수 있음 36 | -------------------------------------------------------------------------------- /structural/decorator/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from datasource import FileDataSource, CompressionDecorator, EncryptionDecorator 3 | 4 | 5 | def test_comp_encryp(): 6 | data_source = FileDataSource("data.txt") 7 | data_source = CompressionDecorator(data_source) 8 | data_source = EncryptionDecorator(data_source) 9 | 10 | data_source.write_data("Hello World") 11 | assert data_source.read_data() == "Hello World" 12 | 13 | 14 | def test_comp(): 15 | data_source = FileDataSource("data.txt") 16 | data_source = CompressionDecorator(data_source) 17 | 18 | data_source.write_data("Hello World") 19 | assert data_source.read_data() == "Hello World" 20 | 21 | 22 | def test_encrypt(): 23 | data_source = FileDataSource("data.txt") 24 | data_source = EncryptionDecorator(data_source) 25 | 26 | data_source.write_data("Hello World") 27 | assert data_source.read_data() == "Hello World" 28 | 29 | 30 | def test_basic(): 31 | data_source = FileDataSource("data.txt") 32 | 33 | data_source.write_data("Hello World") 34 | assert data_source.read_data() == "Hello World" 35 | -------------------------------------------------------------------------------- /creational/abstract_factory/factory.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from button import Button, WindowsButton, MacButton 4 | from checkbox import Checkbox, WindowsCheckbox, MacCheckbox 5 | 6 | 7 | class GUIFactory(ABC): 8 | @classmethod 9 | def of(cls, os: str): 10 | if os == "windows": 11 | return WindowsFactory() 12 | elif os == "mac": 13 | return MacFactory() 14 | else: 15 | raise KeyError("unknown os") 16 | 17 | @abstractmethod 18 | def createButton(self) -> Button: 19 | raise NotImplementedError() 20 | 21 | @abstractmethod 22 | def createCheckbox(self) -> Checkbox: 23 | raise NotImplementedError() 24 | 25 | 26 | class WindowsFactory(GUIFactory): 27 | def createButton(self) -> Button: 28 | return WindowsButton() 29 | 30 | def createCheckbox(self) -> Checkbox: 31 | return WindowsCheckbox() 32 | 33 | 34 | class MacFactory(GUIFactory): 35 | def createButton(self) -> Button: 36 | return MacButton() 37 | 38 | def createCheckbox(self) -> Checkbox: 39 | return MacCheckbox() 40 | -------------------------------------------------------------------------------- /structural/facade/video.py: -------------------------------------------------------------------------------- 1 | class VideoFile(object): 2 | def __init__(self, file): 3 | self.file = file 4 | 5 | 6 | class OggCompressionCodec(object): 7 | pass 8 | 9 | 10 | class MPEG4CompressionCodec(object): 11 | pass 12 | 13 | 14 | class CodecFactory(object): 15 | def __init__(self, file): 16 | self.file = file 17 | 18 | 19 | class BitrateReader(object): 20 | def read(file, codec): 21 | pass 22 | 23 | def convert(buffer, codec): 24 | pass 25 | 26 | 27 | class AudioMixer(object): 28 | def mix(self, result): 29 | return "It's AudioMixer" 30 | 31 | 32 | class VideoConverter(object): 33 | def convert(self, filename, format): 34 | file = VideoFile(filename) 35 | source_codec = CodecFactory(file) 36 | if format == "mp4": 37 | destination_codec = MPEG4CompressionCodec() 38 | else: 39 | destination_codec = OggCompressionCodec() 40 | buffer = BitrateReader.read(file, source_codec) 41 | result = BitrateReader.convert(buffer, destination_codec) 42 | result = AudioMixer().mix(result) 43 | return result 44 | -------------------------------------------------------------------------------- /structural/bridge/remote.py: -------------------------------------------------------------------------------- 1 | from device import Device 2 | 3 | 4 | class Remote(object): 5 | def __init__(self, device, log=False): 6 | self.device = device 7 | self.log = log 8 | 9 | def toggle_power(self): 10 | if self.device.is_enabled(): 11 | self.device.disable() 12 | else: 13 | self.device.enable() 14 | 15 | def volume_up(self): 16 | self.device.volume = self.device.volume + 1 17 | if self.log: 18 | print(f"Volume: {self.device.volume}") 19 | 20 | def volume_down(self): 21 | self.device.volume = self.device.volume - 1 22 | if self.log: 23 | print(f"Volume: {self.device.volume}") 24 | 25 | def channel_up(self): 26 | self.device.channel = self.device.channel + 1 27 | if self.log: 28 | print(f"Channel: {self.device.channel}") 29 | 30 | def channel_down(self): 31 | self.device.channel = self.device.channel - 1 32 | if self.log: 33 | print(f"Channel: {self.device.channel}") 34 | 35 | 36 | class AdvancedRemote(Remote): 37 | def mute(self): 38 | self.device.volume = 0 39 | -------------------------------------------------------------------------------- /creational/builder/README.md: -------------------------------------------------------------------------------- 1 | # Builder Pattern 2 | 3 | https://refactoring.guru/design-patterns/builder 4 | 5 | > ### 복잡한 객체를 스텝별로 구성 6 | 7 | - 총 4가지의 파트로 구성됨. 8 | 9 | 1. Products 10 | - 실제 생성하고 싶은 객체 클래스 11 | 2. Builder 12 | - Abstract Builder 클래스로 product 생성하기 위한 추상 빌드 메소드 제공 13 | 3. Concrete Builder 14 | - Builder 클래스를 상속받음 15 | - Product 를 생성하기 위한 빌더 클래스 16 | - 스텝별로 product 생성하기 위한 conrete 메소드 구현 17 | 4. Director 18 | - Builder 의 메소드들을 이용하여 실제로 클래스를 생성함 19 | - 메소드 호출 순서가 정의됨 20 | 21 | - object 의 구성방식이 다양할 때 사용 22 | - object construction code 를 own class 안이 아닌 분리된 빌더 객체로 추출 23 | - 모든 스텝을 호출하지 않고 필요한 부분만 스텝만 호출함 24 | - 객체 생성 방식이 변경되면 Builder, Concrete Builder, Product 클래스와 무관하게 Director 클래스의 수정만으로 가능 25 | - 클라이언트는 생성하고 싶은 객체의 concrete builder 와 director 의 호출만으로 객체 생성 가능 26 | 27 | ### 적용 상황 28 | 29 | - 객체 생성 시 10개 이상의 파라미터가 필요할 때 30 | - 돌집과 나무집처럼 생성하고 싶은 객체의 스타일에 따라 구성 방식이 달라질 때 31 | - base builder 가 모든 construction step 을 정의하고 concrete builder 에서 스타일에 따른 construction 방식 구현 32 | - 매우 복잡한 객체를 생성할 때 33 | 34 | ### 장점 35 | 36 | - 객체를 단계별로 구성 가능 37 | - 객체의 다양한 스타일을 만들 때 동일한 construction code 재사용 가능 38 | - 제품의 비즈니스 로직과 객체 생성 코드를 분리 가능 (SRP) 39 | 40 | ### 단점 41 | 42 | - 여러 개의 새로운 클래스를 생성해야 하므로 코드의 복잡성 증가 43 | -------------------------------------------------------------------------------- /structural/proxy/README.md: -------------------------------------------------------------------------------- 1 | # Proxy Pattern 2 | 3 | https://refactoring.guru/design-patterns/proxy 4 | 5 | > ### 객체의 접근 권한을 설정, 캐싱 6 | 7 | - 모든 접근이 전부 허용되지 않도록 프록시 클래스가 막아줌 8 | - 기존에 했었던 접근이라면 캐싱된 데이터를 전달하여 객체의 부하를 막음 9 | - 서비스 객체 앞에서 필요한 여러 기능들을 해줄 수 있음 10 | 11 | ### 적용 상황 12 | 13 | - Virtual Proxy: Lazy Initialization 가능 14 | - 시스템 리소스를 낭비하는 무거운 객체가 있을 때 객체 초기화를 정말 필요할 때까지 연기함 15 | - Protection Proxy: 액세스 제어 16 | - 클라이언트의 credential이 일치하는 경우에만 서비스 객체에 요청 전달 17 | - Remote Proxy: 원격 서비스의 로컬 실행 18 | - 서비스 객체가 원격 서버에 있을 때 네트워크를 통해 클라이언트 요청 전달 19 | - 모든 사이 귀찮은 것들을 핸들링함 20 | - 일반적으로 네트워크에서 알고 있는 프록시와 유사 21 | - Logging Proxy: 서비스 객체에 대한 기록 유지 22 | - Caching Proxy: 클라이언트 요청의 결과를 캐싱 23 | - Smart Refrence: 서비스를 아무도 사용하지 않고 있을 때 시스템 리소스 해제 24 | 25 | ### 장점 26 | 27 | - 캡슐화 가능 28 | - 서비스 객체의 라이프사이클 조절 가능 29 | - 서비스 객체를 사용 불가능할 때도 프록시는 사용 가능 30 | - OCP: 서비스나 클라이언트를 변경하지 않고 새 프록시 도입 가능 31 | 32 | ### 단점 33 | 34 | - 코드 복잡성 증가 35 | - 서비스 응답속도 증가 36 | 37 | ### 타 패턴과의 관계 38 | 39 | - Adapter는 다른 interface를, Proxy는 동일한 interface를 Decorator는 향상된 interface를 제공함 40 | - Decorator는 기능 자체가 업그레이드되는 반면, Proxy는 기능 자체는 유지됨 41 | - Facade는 복잡한 엔티티를 버퍼링하고 자체 초기화하는 것이 유사. 42 | - Decorator와 Proxy는 거의 비슷한 구조를 갖고 있으나 Proxy는 자체적으로 서비스 객체의 라이프사이클을 관리하나 Decorator는 항상 클라이언트에 의해 제어됨 43 | -------------------------------------------------------------------------------- /behavioral/strategy/README.md: -------------------------------------------------------------------------------- 1 | # Strategy Pattern 2 | 3 | https://refactoring.guru/design-patterns/strategy 4 | 5 | > ### 객체의 행동(알고리즘)을 컴포넌트화 6 | 7 | - 객체의 행동(알고리즘)을 strategy라는 클래스로 분리하여 객체에 행동을 주입시키는 구조 8 | - 객체의 행동 방식이 다양할 때 이를 컴포넌트화하여 쉽게 변경가능함 9 | - 클라이언트는 Dependency Injection을 통해 context가 concrete strategy에 의존하지 않도록 함 10 | - 일반적으로 client가 concrete를 몰라도 되나 strategy에서는 concrete class를 정확히 알고 있어야 함 11 | 12 | ### 적용 상황 13 | 14 | - 런타임에서 객체 안의 알고리즘을 변경하고 싶을 때 15 | - 여러 객체들끼리 행동 방식만 다를 때 16 | - 클래스 안에 있는 데이터와 알고리즘의 분리(isolation) 17 | 18 | ### 장점 19 | 20 | - 런타임에서 행동 변경 가능 21 | - 알고리즘의 디테일 분리 22 | - OCP: 새로운 알고리즘 추가 시 OCP 만족 23 | 24 | ### 단점 25 | 26 | - 알고리즘 개수가 적거나 변경할 일이 적을 때 무의미함 27 | - 클라이언트가 strategy 간의 차이를 알아야 함 28 | 29 | ### 타 패턴과의 관계 30 | 31 | - Bridge, Strategy, State는 모두 association을 기반으로 한 구조를 가지고 있다. 32 | - 구조적인 관점: bridge가 기본이며 strategy는 거기서 client가 concrete implementation을 의존하며, state는 하나 더 나아가 concrete implementation이 abstraction까지 의존한다. 33 | - 역할의 관점: bridge는 2개의 인터페이스 연결, strategy는 객체의 행동 변경, state는 객체의 state에 따른 행동 변경이다. 34 | - TODO: Command 35 | - TODO: Decorator 36 | - TODO: Template Method 37 | - state는 strategy의 확장판이다. 2개 다 context가 직접 행동하지 않으며 concrete class에게 delegate한다. 그러나 strategy는 concrete class 간의 의존성이 전혀 없으나 state는 자유롭게 서로 변경 가능하도록 한다. 38 | -------------------------------------------------------------------------------- /structural/composite/graphic.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List 3 | 4 | 5 | class Graphic(ABC): 6 | @abstractmethod 7 | def draw(self): 8 | pass 9 | 10 | @abstractmethod 11 | def move(self, x, y): 12 | pass 13 | 14 | 15 | class Dot(Graphic): 16 | def __init__(self, x, y): 17 | self.x = x 18 | self.y = y 19 | 20 | def draw(self): 21 | return f"{self.x}, {self.y}: ." 22 | 23 | def move(self, x, y): 24 | self.x = x 25 | self.y = y 26 | 27 | 28 | class Circle(Dot): 29 | def __init__(self, x, y, r): 30 | super().__init__(x, y) 31 | self.r = r 32 | 33 | def draw(self): 34 | return f"{self.x}, {self.y}: o" 35 | 36 | 37 | class CompoundGraphic(Graphic): 38 | def __init__(self, *graphics: List[Graphic]): 39 | self.children = list(graphics) 40 | 41 | def add(self, graphic: Graphic): 42 | self.children.append(graphic) 43 | 44 | def remove(self, graphic: Graphic): 45 | self.children.remove(graphic) 46 | 47 | def draw(self): 48 | return "\n".join([graphic.draw() for graphic in self.children]) 49 | 50 | def move(self, x, y): 51 | for graphic in self.children: 52 | graphic.move(x, y) 53 | -------------------------------------------------------------------------------- /creational/simple_factory/main.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class User(ABC): 5 | @abstractmethod 6 | def action(self): 7 | raise NotImplementedError() 8 | 9 | 10 | class AdminUser(User): 11 | def __init__(self): 12 | self.__type = "admin" 13 | 14 | def get_type(self) -> str: 15 | return self.__type 16 | 17 | def action(self): 18 | print("[+] admin user action") 19 | 20 | def __str__(self) -> str: 21 | return "admin user" 22 | 23 | 24 | class NormalUser(User): 25 | def __init__(self): 26 | self.__type = "normal" 27 | 28 | def get_type(self) -> str: 29 | return self.__type 30 | 31 | def action(self): 32 | print("[+] normal user action") 33 | 34 | def __str__(self) -> str: 35 | return "normal user" 36 | 37 | 38 | class UserFactory(object): 39 | @staticmethod 40 | def create_user(user_type: str) -> User: 41 | if user_type == "admin": 42 | return AdminUser() 43 | elif user_type == "normal": 44 | return NormalUser() 45 | else: 46 | raise KeyError("unknown user type") 47 | 48 | 49 | if __name__ == "__main__": 50 | user = UserFactory.create_user("admin") 51 | print("[+] type:", user.get_type()) 52 | user.action() 53 | -------------------------------------------------------------------------------- /behavioral/state/README.md: -------------------------------------------------------------------------------- 1 | # Pattern 2 | 3 | https://refactoring.guru/design-patterns/ 4 | 5 | > ### 객체의 내부 상태 변경에 따라 행동(알고리즘) 변경 6 | 7 | - strategy의 확장판 8 | - stateful한 객체에 대해서 사용가능하며 객체의 state에 따라 객체의 행동 방식을 변경함 9 | - strategy 패턴에서 concrete class에서 context로의 의존성이 하나 추가되는데 strategy interface로 인해 순환의존성 문제를 가지지 않음 10 | - 객체의 메소드 하나만을 주입하는 것이 아니라 객체에서 stateful한 모든 메소드에 대해서 concrete state class로 의존성 주입함 11 | 12 | ### 적용 상황 13 | 14 | - stateful한 객체(stateful한 메소드가 존재하는 객체)일 때 15 | - state의 개수가 많을 때 16 | - 클래스 변수의 값에 따른 condition으로 클래스가 오염되었을 때 17 | - 유사한 state 간에 중복 코드가 많을 때 state interface와 concrete state class 사이의 abstract base class를 구현하여 state패턴 적극 사용 가능 18 | 19 | ### 장점 20 | 21 | - SRP: 각 state클래스가 하나의 역할만을 맡음 22 | - OCP: 새로운 state 확장에 열려있음 23 | - 복잡한 state machine을 간단하게 만들 수 있음 24 | 25 | ### 단점 26 | 27 | - state가 몇 개 없거나 변경이 적을 땐 오버엔지니어링일 수 있음 28 | 29 | ### 타 패턴과의 관계 30 | 31 | - Bridge, Strategy, State는 모두 association을 기반으로 한 구조를 가지고 있다. 32 | - 구조적인 관점: bridge가 기본이며 strategy는 거기서 client가 concrete implementation을 의존하며, state는 하나 더 나아가 concrete implementation이 abstraction까지 의존한다. 33 | - 역할의 관점: bridge는 2개의 인터페이스 연결, strategy는 객체의 행동 변경, state는 객체의 state에 따른 행동 변경이다. 34 | - state는 strategy의 확장판이다. 2개 다 context가 직접 행동하지 않으며 concrete class에게 delegate한다. 그러나 strategy는 concrete class 간의 의존성이 전혀 없으나 state는 자유롭게 서로 변경 가능하도록 한다. 35 | -------------------------------------------------------------------------------- /structural/composite/main.py: -------------------------------------------------------------------------------- 1 | from graphic import CompoundGraphic, Dot, Circle 2 | 3 | 4 | class ImageEditor(object): 5 | def __init__(self): 6 | self.all = CompoundGraphic() 7 | 8 | def add_dot(self, x, y): 9 | dot = Dot(x, y) 10 | self.all.add(dot) 11 | return dot 12 | 13 | def add_circle(self, x, y, r): 14 | circle = Circle(x, y, r) 15 | self.all.add(circle) 16 | return circle 17 | 18 | def group_selected(self, *graphics): 19 | self.graphic = CompoundGraphic() 20 | for graphic in graphics: 21 | self.graphic.add(graphic) 22 | self.all.remove(graphic) 23 | self.all.add(self.graphic) 24 | return self.graphic 25 | 26 | def move(self, x, y, *graphics): 27 | for graphic in graphics: 28 | graphic.move(x, y) 29 | 30 | def draw(self): 31 | return self.all.draw() 32 | 33 | 34 | if __name__ == "__main__": 35 | editor = ImageEditor() 36 | dot1 = editor.add_dot(10, 10) 37 | dot2 = editor.add_dot(5, 2) 38 | circle = editor.add_circle(20, 20, 10) 39 | print(editor.draw()) 40 | 41 | editor.move(0, 0, dot1) 42 | print(editor.draw()) 43 | 44 | group = editor.group_selected(dot1, circle) 45 | editor.move(-5, -5, group) 46 | print(editor.draw()) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design-Patterns 2 | 3 | ## Creational 4 | 5 | 1. [Simple Factory Pattern](creational/simple_factory/README.md) 6 | 2. [Factory Method Pattern](creational/factory_method/README.md) 7 | 3. [Abstract Factory Pattern](creational/abstract_factory/README.md) 8 | 4. [Builder Pattern](creational/builder/README.md) 9 | 5. [Prototype Pattern](creational/prototype/README.md) 10 | 6. [Singleton Pattern](creational/singleton/README.md) 11 | 12 | ## Structural 13 | 14 | 1. [Adapter Pattern](structural/adapter/README.md) 15 | 2. [Bridge Pattern](structural/bridge/README.md) 16 | 3. [Composite Pattern](structural/composite/README.md) 17 | 4. [Decorator Pattern](structural/decorator/README.md) 18 | 5. [Facade Pattern](structural/facade/README.md) 19 | 6. [Flyweight Pattern](structural/flyweight/README.md) 20 | 7. [Proxy Pattern](structural/proxy/README.md) 21 | 22 | ## Behavioral 23 | 24 | 1. [Strategy Pattern](behavioral/strategy/README.md) 25 | 2. [State Pattern](behavioral/state/README.md) 26 | 3. [Chain of Responsibility Pattern](behavioral/chain_of_responsibility/README.md) 27 | 4. [Command Pattern](behavioral/command/README.md) 28 | 5. [Iterator Pattern](behavioral/iterator/README.md) 29 | 6. [Mediator Pattern](behavioral/mediator/README.md) 30 | 7. [Memento Pattern](behavioral/memento/README.md) 31 | 8. [Observer Pattern](behavioral/observer/README.md) 32 | 9. [Template Method Pattern](behavioral/template_method/README.md) 33 | 10. [Visitor Pattern](behavioral/visitor/README.md) 34 | -------------------------------------------------------------------------------- /structural/proxy/youtube.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class ThirdPartyYoutubeLib(ABC): 5 | def __init__(self): 6 | self.download_count = 0 7 | 8 | @abstractmethod 9 | def list_videos(self, video): 10 | raise NotImplementedError 11 | 12 | @abstractmethod 13 | def download_video(self, id): 14 | raise NotImplementedError 15 | 16 | def get_download_count(self): 17 | return self.download_count 18 | 19 | 20 | class ThirdPartyYoutube(ThirdPartyYoutubeLib): 21 | DB = {} 22 | 23 | def list_videos(self): 24 | return self.DB 25 | 26 | def download_video(self, id): 27 | self.DB[id] = "video" + str(id) 28 | self.download_count += 1 29 | print("Downloading... It works takes an hour") 30 | return self.DB[id] 31 | 32 | 33 | class CachedYoutube(ThirdPartyYoutubeLib): 34 | video_cache = {} 35 | 36 | def __init__(self, service: ThirdPartyYoutube): 37 | super().__init__() 38 | self.service = service 39 | 40 | def list_videos(self): 41 | return self.service.list_videos() 42 | 43 | def download_video(self, id): 44 | if id in self.video_cache: 45 | return self.video_cache[id] 46 | else: 47 | video = self.service.download_video(id) 48 | self.video_cache[id] = video 49 | return video 50 | 51 | def get_download_count(self): 52 | return self.service.get_download_count() 53 | -------------------------------------------------------------------------------- /behavioral/state/state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from player import Player 3 | 4 | 5 | class State(ABC): 6 | def __init__(self, player: Player): 7 | self.player = player 8 | 9 | @abstractmethod 10 | def click_lock(self): 11 | pass 12 | 13 | @abstractmethod 14 | def click_play(self): 15 | pass 16 | 17 | @abstractmethod 18 | def click_next(self): 19 | pass 20 | 21 | @abstractmethod 22 | def click_previous(self): 23 | pass 24 | 25 | 26 | class ReadyState(State): 27 | def click_lock(self): 28 | self.player.change_state(LockedState(self.player)) 29 | 30 | def click_play(self): 31 | self.player.change_state(PlayingState(self.player)) 32 | 33 | def click_next(self): 34 | print("Move on to the next song") 35 | 36 | def click_previous(self): 37 | print("Move on to the previous song") 38 | 39 | 40 | class PlayingState(State): 41 | def click_lock(self): 42 | self.player.change_state(LockedState(self.player)) 43 | 44 | def click_play(self): 45 | self.player.change_state(ReadyState(self.player)) 46 | 47 | def click_next(self): 48 | print("Play the next song") 49 | 50 | def click_previous(self): 51 | print("Play the previous song") 52 | 53 | 54 | class LockedState(State): 55 | def click_lock(self): 56 | self.player.change_state(ReadyState(self.player)) 57 | 58 | def click_play(self): 59 | pass 60 | 61 | def click_next(self): 62 | pass 63 | 64 | def click_previous(self): 65 | pass 66 | -------------------------------------------------------------------------------- /structural/decorator/README.md: -------------------------------------------------------------------------------- 1 | # Decorator Pattern 2 | 3 | https://refactoring.guru/design-patterns/decorator 4 | 5 | > ### wrapper object를 통해 추가 행동 확장 6 | 7 | - 일반적으로 객체의 행동 확장을 위해서는 상속이 많이 쓰임. 상속의 단점 8 | - 상속은 static해서 런타임에서 수정할 수 없다. 다시 만들고 대체해야 한다. 9 | - 자식 클래스는 단 하나의 부모 클래스만을 가진다. 10 | - 상속 대신 Aggregation 을 사용 11 | - Composite과 마찬가지로 inheritance와 aggregation을 동시에 사용하는 디자인 패턴 12 | 13 | - Association: 부모객체가 자식객체를 소유 + 각자의 라이프사이클을 가짐 14 | 15 | ``` 16 | class Child(): 17 | pass 18 | 19 | class Parent(): 20 | def __init__(self, child): 21 | self.child = child 22 | ``` 23 | 24 | - Composition: 부모객체가 자식객체를 소유 + 부모객체만 라이브사이클 가짐 25 | 26 | ``` 27 | class Child(): 28 | pass 29 | 30 | class Parent(): 31 | def __init__(self): 32 | self.child = Child() 33 | ``` 34 | 35 | - 필요한 행동이 10개가 있을 때 2\*\*10개 조합의 자식 클래스를 생성하지 말고 10개의 데코레이터만 생성해서 자유롭게 런타임에서 추가, 삭제할 수 있도록 함. 36 | 37 | ### 적용 상황 38 | 39 | - 런타임에서 객체에 행동 추가가 필요할 때 40 | - 상속을 통해 행동 추가가 불가능하거나 어색할 때 41 | 42 | ### 장점 43 | 44 | - 새로운 subclass를 만들지 않고 행동 확장이 가능함 45 | - 런타임에서 객체의 책임을 삭제하거나 추가할 수 있음 46 | - 멀티 데코레이터를 통해 다양한 행동을 조합할 수 있음 47 | - SRP: 모놀리틱 클래스를 다양한 행동들로 쪼갤 수 있음 48 | 49 | ### 단점 50 | 51 | - wrapper가 여러개 있을 때 중간에 있는거 삭제가 어려움 52 | - 데코레이터 스택의 순서에 따라 동작이 달라지지 않을 때 구현하기 어려움 53 | - 데코레이터 초기 구성 코드는 흉할 수 있음 54 | 55 | ### 타 패턴과의 관계 56 | 57 | - Adapter는 존재하는 객체의 인터페이스를 변경하지만 Decorator는 인터페이스 변경 없이 확장한다. Decorator는 recursive composition을 지원한다. 58 | - Adapter는 인터페이스 변경, Proxy는 인터페이스 유지, Decorator는 인터페이스 확장 59 | - TODO: CoR 60 | - open-ended number of objects를 구성하기 위해 Composite, Decorator는 recursive composition 사용 61 | - Decorator는 Composite과 유사하나 단 하나의 자식 컴포넌트만 갖고 있는 것이 다르다. 62 | - Decorator는 책임이 추가되지만 Composite는 단순히 더하기만 한다. 63 | - Composite에서 각 객체의 행동을 확장하기 위해 Decorator 사용 가능 64 | - open-ended number of objects를 다루는 디자인 패턴에서는 prototype을 같이 사용하면 좋다. 복잡한 구조를 매번 재클론하는 것은 어렵다. 65 | - TODO: Strategy 66 | - TODO: Proxy 67 | -------------------------------------------------------------------------------- /structural/decorator/datasource.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List 3 | 4 | import base64 5 | from Crypto.Cipher import AES 6 | import zlib 7 | 8 | 9 | class DataSource(ABC): 10 | @abstractmethod 11 | def write_data(self): 12 | pass 13 | 14 | @abstractmethod 15 | def read_data(self): 16 | pass 17 | 18 | 19 | class FileDataSource(DataSource): 20 | def __init__(self, filename): 21 | self.filename = filename 22 | 23 | def write_data(self, data: str): 24 | with open(self.filename, "w") as f: 25 | f.write(data) 26 | 27 | def read_data(self) -> str: 28 | with open(self.filename, "r") as f: 29 | return f.read() 30 | 31 | 32 | class DataSourceDecorator(DataSource): 33 | def __init__(self, data_source): 34 | self.data_source = data_source 35 | 36 | def write_data(self, data: str): 37 | self.data_source.write_data(data) 38 | 39 | def read_data(self) -> str: 40 | return self.data_source.read_data() 41 | 42 | 43 | class EncryptionDecorator(DataSourceDecorator): 44 | secret_key = b"1234567890123456" 45 | 46 | def write_data(self, data: str): 47 | self.write_cipher = AES.new(self.secret_key, AES.MODE_EAX) 48 | super().write_data( 49 | base64.b64encode(self.write_cipher.encrypt(data.encode())).decode() 50 | ) 51 | 52 | def read_data(self) -> str: 53 | cipher = AES.new(self.secret_key, AES.MODE_EAX, self.write_cipher.nonce) 54 | return cipher.decrypt(base64.b64decode(super().read_data())).decode() 55 | 56 | 57 | class CompressionDecorator(DataSourceDecorator): 58 | def write_data(self, data: str): 59 | super().write_data(base64.b64encode(zlib.compress(data.encode())).decode()) 60 | 61 | def read_data(self) -> str: 62 | return zlib.decompress( 63 | base64.b64decode(bytes(super().read_data().encode())) 64 | ).decode() 65 | -------------------------------------------------------------------------------- /structural/flyweight/tree.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class TreeType(object): 5 | def __init__(self, name: str, color: str, texture: str): 6 | self.__name = name 7 | self.__color = color 8 | self.__texture = texture 9 | 10 | @property 11 | def name(self) -> str: 12 | return self.__name 13 | 14 | @property 15 | def color(self) -> str: 16 | return self.__color 17 | 18 | @property 19 | def texture(self) -> str: 20 | return self.__texture 21 | 22 | def draw(self, canvas, x, y): 23 | print(self.__name, x, y) 24 | 25 | 26 | class Tree(object): 27 | def __init__(self, x: int, y: int, tree_type: TreeType): 28 | self.x = x 29 | self.y = y 30 | self.tree_type = tree_type 31 | 32 | def draw(self, canvas): 33 | self.tree_type.draw(canvas, self.x, self.y) 34 | 35 | 36 | class TreeFactory(object): 37 | tree_types: List[TreeType] = [] 38 | 39 | @staticmethod 40 | def get_tree_type(name, color, texture): 41 | for tree_type in TreeFactory.tree_types: 42 | if ( 43 | tree_type.name == name 44 | and tree_type.color == color 45 | and tree_type.texture == texture 46 | ): 47 | print(f"caching tree type: {name}, {color}, {texture}") 48 | return tree_type 49 | 50 | tree_type = TreeType(name, color, texture) 51 | TreeFactory.tree_types.append(tree_type) 52 | return tree_type 53 | 54 | 55 | class Forest(object): 56 | def __init__(self): 57 | self.trees: List[Tree] = [] 58 | 59 | def plant_tree(self, x: int, y: int, name: str, color: str, texture: str) -> Tree: 60 | tree_type = TreeFactory.get_tree_type(name, color, texture) 61 | if tree_type is None: 62 | return None 63 | tree = Tree(x, y, tree_type) 64 | self.trees.append(tree) 65 | return tree 66 | 67 | def draw(self, canvas): 68 | for tree in self.trees: 69 | tree.draw(canvas) 70 | -------------------------------------------------------------------------------- /creational/builder/builder.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from engine import Engine 3 | 4 | 5 | class Car(object): 6 | seats = 0 7 | engine = None 8 | trip_computer = False 9 | gps = False 10 | 11 | def __str__(self): 12 | ret = "" 13 | ret += f"Car with {self.seats} seats, {self.engine.get_type()} engine\n" 14 | if self.trip_computer: 15 | ret += "with trip computer\n" 16 | if self.gps: 17 | ret += "with GPS\n" 18 | return ret 19 | 20 | 21 | class Manual(object): 22 | seats = 0 23 | engine = None 24 | trip_computer = False 25 | gps = False 26 | 27 | def __str__(self): 28 | ret = "" 29 | ret += f"Car with {self.seats} seats, {self.engine.get_type()} engine\n" 30 | if self.trip_computer: 31 | ret += "with trip computer\n" 32 | if self.gps: 33 | ret += "with GPS\n" 34 | 35 | return ret 36 | 37 | 38 | class Builder(ABC): 39 | @abstractmethod 40 | def reset(self): 41 | raise NotImplementedError() 42 | 43 | @abstractmethod 44 | def setSeats(self, seats: int): 45 | raise NotImplementedError() 46 | 47 | @abstractmethod 48 | def setEngine(self, engine: Engine): 49 | raise NotImplementedError() 50 | 51 | @abstractmethod 52 | def setTripComputer(self): 53 | raise NotImplementedError() 54 | 55 | @abstractmethod 56 | def setGPS(self): 57 | raise NotImplementedError() 58 | 59 | 60 | class CarBuilder(Builder): 61 | def __init__(self): 62 | self.reset() 63 | 64 | def reset(self): 65 | self.__car = Car() 66 | 67 | def setSeats(self, seats: int): 68 | self.__car.seats = seats 69 | 70 | def setEngine(self, engine: Engine): 71 | self.__car.engine = engine 72 | 73 | def setTripComputer(self): 74 | self.__car.trip_computer = True 75 | 76 | def setGPS(self): 77 | self.__car.gps = True 78 | 79 | def getResult(self): 80 | return self.__car 81 | 82 | 83 | class CarManualBuilder(Builder): 84 | def __init__(self): 85 | self.reset() 86 | 87 | def reset(self): 88 | self.__manual = Manual() 89 | 90 | def setSeats(self, seats: int): 91 | self.__manual.seats = seats 92 | 93 | def setEngine(self, engine: Engine): 94 | self.__manual.engine = engine 95 | 96 | def setTripComputer(self): 97 | self.__manual.trip_computer = True 98 | 99 | def setGPS(self): 100 | self.__manual.gps = True 101 | 102 | def getResult(self): 103 | return self.__manual 104 | --------------------------------------------------------------------------------