├── .ruby-version ├── src ├── abstract_factory │ ├── real_world │ │ ├── output.txt │ │ └── main.rb │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── singleton │ └── conceptual │ │ ├── non_thread_safe │ │ ├── output.txt │ │ └── main.rb │ │ └── thread_safe │ │ ├── output.txt │ │ └── main.rb ├── iterator │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── adapter │ ├── real_world │ │ ├── output.txt │ │ └── main.rb │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── facade │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── builder │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── decorator │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── bridge │ ├── conceptual │ │ ├── output.txt │ │ └── main.rb │ └── real_world │ │ ├── output.txt │ │ └── main.rb ├── prototype │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── visitor │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── strategy │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── proxy │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── composite │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── state │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── mediator │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── command │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── factory_method │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── chain_of_responsibility │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── observer │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── flyweight │ └── conceptual │ │ ├── output.txt │ │ └── main.rb ├── template_method │ └── conceptual │ │ ├── output.txt │ │ └── main.rb └── memento │ └── conceptual │ ├── output.txt │ └── main.rb ├── Gemfile ├── .travis.yml ├── .rubocop.yml ├── Gemfile.lock └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /src/abstract_factory/real_world/output.txt: -------------------------------------------------------------------------------- 1 | WindowsButton has been drawn 2 | WindowsCheckbox has been drawn 3 | -------------------------------------------------------------------------------- /src/singleton/conceptual/non_thread_safe/output.txt: -------------------------------------------------------------------------------- 1 | Singleton works, both variables contain the same instance. -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '~>3.2.2' 4 | 5 | group :development, :test do 6 | gem 'rubocop' 7 | end 8 | -------------------------------------------------------------------------------- /src/iterator/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Straight traversal: 2 | First 3 | Second 4 | Third 5 | 6 | Reverse traversal: 7 | Third 8 | Second 9 | First 10 | -------------------------------------------------------------------------------- /src/adapter/real_world/output.txt: -------------------------------------------------------------------------------- 1 | (90km/h) You are bellow the max limit 2 | (110km/h) You are speeding 3 | (80.5km/h) You are bellow the max limit 4 | (128.8km/h) You are speeding 5 | -------------------------------------------------------------------------------- /src/facade/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Facade initializes subsystems: 2 | Subsystem1: Ready! 3 | Subsystem2: Get ready! 4 | Facade orders subsystems to perform the action: 5 | Subsystem1: Go! 6 | Subsystem2: Fire! -------------------------------------------------------------------------------- /src/singleton/conceptual/thread_safe/output.txt: -------------------------------------------------------------------------------- 1 | If you see the same value, then singleton was reused (yay!) 2 | If you see different values, then 2 singletons were created (booo!!) 3 | 4 | RESULT: 5 | 6 | FOO 7 | FOO 8 | -------------------------------------------------------------------------------- /src/builder/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Standard basic product: 2 | Product parts: PartA1 3 | 4 | Standard full featured product: 5 | Product parts: PartA1, PartB1, PartC1 6 | 7 | Custom product: 8 | Product parts: PartA1, PartB1 -------------------------------------------------------------------------------- /src/decorator/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: I've got a simple component: 2 | RESULT: ConcreteComponent 3 | 4 | Client: Now I've got a decorated component: 5 | RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 3.2.2 4 | sudo: required 5 | dist: trusty 6 | cache: bundler 7 | before_install: 8 | - gem install bundler 9 | install: 10 | - bundle install 11 | script: 12 | - bundle exec rubocop 13 | -------------------------------------------------------------------------------- /src/bridge/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Abstraction: Base operation with: 2 | ConcreteImplementationA: Here's the result on the platform A. 3 | 4 | ExtendedAbstraction: Extended operation with: 5 | ConcreteImplementationB: Here's the result on the platform B. -------------------------------------------------------------------------------- /src/prototype/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Primitive field values have been carried over to a clone. Yay! 2 | Simple component has been cloned. Yay! 3 | Component with back reference has been cloned. Yay! 4 | Component with back reference is linked to the clone. Yay! -------------------------------------------------------------------------------- /src/visitor/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | The client code works with all visitors via the base Visitor interface: 2 | A + ConcreteVisitor1 3 | B + ConcreteVisitor1 4 | It allows the same client code to work with different types of visitors: 5 | A + ConcreteVisitor2 6 | B + ConcreteVisitor2 7 | -------------------------------------------------------------------------------- /src/strategy/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: Strategy is set to normal sorting. 2 | Context: Sorting data using the strategy (not sure how it'll do it) 3 | a,b,c,d,e 4 | 5 | Client: Strategy is set to reverse sorting. 6 | Context: Sorting data using the strategy (not sure how it'll do it) 7 | e,d,c,b,a -------------------------------------------------------------------------------- /src/proxy/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: Executing the client code with a real subject: 2 | RealSubject: Handling request. 3 | 4 | Client: Executing the same client code with a proxy: 5 | Proxy: Checking access prior to firing a real request. 6 | RealSubject: Handling request. 7 | Proxy: Logging the time of request. -------------------------------------------------------------------------------- /src/composite/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: I've got a simple component: 2 | RESULT: Leaf 3 | 4 | Client: Now I've got a composite tree: 5 | RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)) 6 | 7 | Client: I don't need to check the components classes even when managing the tree: 8 | RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) -------------------------------------------------------------------------------- /src/state/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Context: Transition to ConcreteStateA 2 | ConcreteStateA handles request1. 3 | ConcreteStateA wants to change the state of the context. 4 | Context: Transition to ConcreteStateB 5 | ConcreteStateB handles request2. 6 | ConcreteStateB wants to change the state of the context. 7 | Context: Transition to ConcreteStateA 8 | -------------------------------------------------------------------------------- /src/mediator/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client triggers operation A. 2 | Component 1 does A. 3 | Mediator reacts on A and triggers following operations: 4 | Component 2 does C. 5 | 6 | Client triggers operation D. 7 | Component 2 does D. 8 | Mediator reacts on D and triggers following operations: 9 | Component 1 does B. 10 | Component 2 does C. 11 | -------------------------------------------------------------------------------- /src/adapter/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: I can work just fine with the Target objects: 2 | Target: The default target's behavior. 3 | 4 | Client: The Adaptee class has a weird interface. See, I don't understand it: 5 | Adaptee: .eetpadA eht fo roivaheb laicepS 6 | 7 | Client: But I can work with it via the Adapter: 8 | Adapter: (TRANSLATED) Special behavior of the Adaptee. -------------------------------------------------------------------------------- /src/abstract_factory/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Client: Testing client code with the first factory type: 2 | The result of the product B1. 3 | The result of the B1 collaborating with the (The result of the product A1.) 4 | 5 | Client: Testing the same client code with the second factory type: 6 | The result of the product B2. 7 | The result of the B2 collaborating with the (The result of the product A2.) 8 | -------------------------------------------------------------------------------- /src/command/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Invoker: Does anybody want something done before I begin? 2 | SimpleCommand: See, I can do simple things like printing (Say Hi!) 3 | Invoker: ...doing something really important... 4 | Invoker: Does anybody want something done after I finish? 5 | ComplexCommand: Complex stuff should be done by a receiver object 6 | Receiver: Working on (Send email.) 7 | Receiver: Also working on (Save report.) -------------------------------------------------------------------------------- /src/factory_method/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | App: Launched with the ConcreteCreator1. 2 | Client: I'm not aware of the creator's class, but it still works. 3 | Creator: The same creator's code has just worked with {Result of the ConcreteProduct1} 4 | 5 | App: Launched with the ConcreteCreator2. 6 | Client: I'm not aware of the creator's class, but it still works. 7 | Creator: The same creator's code has just worked with {Result of the ConcreteProduct2} -------------------------------------------------------------------------------- /src/chain_of_responsibility/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Chain: Monkey > Squirrel > Dog 2 | 3 | Client: Who wants a Nut? 4 | Squirrel: I'll eat the Nut 5 | Client: Who wants a Banana? 6 | Monkey: I'll eat the Banana 7 | Client: Who wants a Cup of coffee? 8 | Cup of coffee was left untouched. 9 | 10 | Subchain: Squirrel > Dog 11 | 12 | Client: Who wants a Nut? 13 | Squirrel: I'll eat the Nut 14 | Client: Who wants a Banana? 15 | Banana was left untouched. 16 | Client: Who wants a Cup of coffee? 17 | Cup of coffee was left untouched. -------------------------------------------------------------------------------- /src/observer/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Subject: Attached an observer. 2 | Subject: Attached an observer. 3 | 4 | Subject: I'm doing something important. 5 | Subject: My state has just changed to: 1 6 | Subject: Notifying observers... 7 | ConcreteObserverA: Reacted to the event 8 | 9 | Subject: I'm doing something important. 10 | Subject: My state has just changed to: 10 11 | Subject: Notifying observers... 12 | ConcreteObserverB: Reacted to the event 13 | 14 | Subject: I'm doing something important. 15 | Subject: My state has just changed to: 2 16 | Subject: Notifying observers... 17 | ConcreteObserverB: Reacted to the event 18 | -------------------------------------------------------------------------------- /src/flyweight/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | FlyweightFactory: I have 5 flyweights: 2 | Camaro2018_Chevrolet_pink 3 | C300_Mercedes Benz_black 4 | C500_Mercedes Benz_red 5 | BMW_M5_red 6 | BMW_X6_white 7 | 8 | Client: Adding a car to database. 9 | FlyweightFactory: Reusing existing flyweight. 10 | Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state. 11 | 12 | Client: Adding a car to database. 13 | FlyweightFactory: Can't find a flyweight, creating new one. 14 | Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state. 15 | 16 | FlyweightFactory: I have 6 flyweights: 17 | Camaro2018_Chevrolet_pink 18 | C300_Mercedes Benz_black 19 | C500_Mercedes Benz_red 20 | BMW_M5_red 21 | BMW_X6_white 22 | BMW_X1_red -------------------------------------------------------------------------------- /src/template_method/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Same client code can work with different subclasses: 2 | AbstractClass says: I am doing the bulk of the work 3 | ConcreteClass1 says: Implemented Operation1 4 | AbstractClass says: But I let subclasses override some operations 5 | ConcreteClass1 says: Implemented Operation2 6 | AbstractClass says: But I am doing the bulk of the work anyway 7 | 8 | Same client code can work with different subclasses: 9 | AbstractClass says: I am doing the bulk of the work 10 | ConcreteClass2 says: Implemented Operation1 11 | AbstractClass says: But I let subclasses override some operations 12 | ConcreteClass2 says: Overridden Hook1 13 | ConcreteClass2 says: Implemented Operation2 14 | AbstractClass says: But I am doing the bulk of the work anyway 15 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 3.2 3 | 4 | Style/AsciiComments: 5 | Enabled: false 6 | 7 | Style/FrozenStringLiteralComment: 8 | Enabled: false 9 | 10 | Style/TrivialAccessors: 11 | Enabled: false 12 | 13 | Style/Documentation: 14 | Enabled: false 15 | 16 | Style/AccessModifierDeclarations: 17 | Enabled: false 18 | 19 | Metrics/LineLength: 20 | Description: 'Limit lines to 80 characters.' 21 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 22 | Max: 90 23 | 24 | Metrics/ParameterLists: 25 | Enabled: false 26 | 27 | Naming/MethodParameterName: 28 | Enabled: false 29 | 30 | Lint/DuplicateMethods: 31 | Enabled: false 32 | 33 | Lint/UnusedMethodArgument: 34 | Enabled: false 35 | 36 | Lint/MissingSuper: 37 | Enabled: false 38 | -------------------------------------------------------------------------------- /src/bridge/real_world/output.txt: -------------------------------------------------------------------------------- 1 | Tests with basic remote. 2 | Remote: power toggle 3 | ------------------------------------ 4 | | I'm radio. 5 | | I'm enabled 6 | | Current volume is 30% 7 | | Current channel is 1 8 | ------------------------------------ 9 | 10 | Tests with advanced remote. 11 | Remote: power toggle 12 | ------------------------------------ 13 | | I'm radio. 14 | | I'm disabled 15 | | Current volume is 0% 16 | | Current channel is 1 17 | ------------------------------------ 18 | 19 | Tests with basic remote. 20 | Remote: power toggle 21 | ------------------------------------ 22 | | I'm a TV. 23 | | I'm enabled 24 | | Current volume is 30% 25 | | Current channel is 1 26 | ------------------------------------ 27 | Tests with advanced remote. 28 | Remote: power toggle 29 | ------------------------------------ 30 | | I'm a TV. 31 | | I'm disabled 32 | | Current volume is 0% 33 | | Current channel is 1 34 | ------------------------------------ 35 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.2) 5 | base64 (0.1.1) 6 | json (2.6.3) 7 | language_server-protocol (3.17.0.3) 8 | parallel (1.23.0) 9 | parser (3.2.2.3) 10 | ast (~> 2.4.1) 11 | racc 12 | racc (1.7.1) 13 | rainbow (3.1.1) 14 | regexp_parser (2.8.1) 15 | rexml (3.2.8) 16 | strscan (>= 3.0.9) 17 | rubocop (1.56.0) 18 | base64 (~> 0.1.1) 19 | json (~> 2.3) 20 | language_server-protocol (>= 3.17.0) 21 | parallel (~> 1.10) 22 | parser (>= 3.2.2.3) 23 | rainbow (>= 2.2.2, < 4.0) 24 | regexp_parser (>= 1.8, < 3.0) 25 | rexml (>= 3.2.5, < 4.0) 26 | rubocop-ast (>= 1.28.1, < 2.0) 27 | ruby-progressbar (~> 1.7) 28 | unicode-display_width (>= 2.4.0, < 3.0) 29 | rubocop-ast (1.29.0) 30 | parser (>= 3.2.1.0) 31 | ruby-progressbar (1.13.0) 32 | strscan (3.1.0) 33 | unicode-display_width (2.4.2) 34 | 35 | PLATFORMS 36 | ruby 37 | 38 | DEPENDENCIES 39 | rubocop 40 | 41 | RUBY VERSION 42 | ruby 3.2.2p53 43 | 44 | BUNDLED WITH 45 | 2.4.18 46 | -------------------------------------------------------------------------------- /src/memento/conceptual/output.txt: -------------------------------------------------------------------------------- 1 | Originator: My initial state is: Super-duper-super-puper-super. 2 | 3 | Caretaker: Saving Originator's state... 4 | Originator: I'm doing something important. 5 | Originator: and my state has changed to: CHYzYSIWbqvWkCzIHOqTyEJWfQlFMn 6 | 7 | Caretaker: Saving Originator's state... 8 | Originator: I'm doing something important. 9 | Originator: and my state has changed to: vbkhwCeAEQBpLwQLlhmpcvUnwzxVnT 10 | 11 | Caretaker: Saving Originator's state... 12 | Originator: I'm doing something important. 13 | Originator: and my state has changed to: SBWlQnAEPLsitiOQAZbGlXHZAeWBoW 14 | 15 | Caretaker: Here's the list of mementos: 16 | 2023-08-11 15:02:35 / (Super-dup...) 17 | 2023-08-11 15:02:35 / (CHYzYSIWb...) 18 | 2023-08-11 15:02:35 / (vbkhwCeAE...) 19 | 20 | Client: Now, let's rollback! 21 | Caretaker: Restoring state to: 2023-08-11 15:02:35 / (vbkhwCeAE...) 22 | Originator: My state has changed to: vbkhwCeAEQBpLwQLlhmpcvUnwzxVnT 23 | 24 | Client: Once more! 25 | Caretaker: Restoring state to: 2023-08-11 15:02:35 / (CHYzYSIWb...) 26 | Originator: My state has changed to: CHYzYSIWbqvWkCzIHOqTyEJWfQlFMn 27 | -------------------------------------------------------------------------------- /src/singleton/conceptual/non_thread_safe/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Singleton Design Pattern 2 | # 3 | # Intent: Lets you ensure that a class has only one instance, while providing a 4 | # global access point to this instance. 5 | # 6 | # RU: Паттерн Одиночка 7 | # 8 | # Назначение: Гарантирует, что у класса есть только один экземпляр, и 9 | # предоставляет к нему глобальную точку доступа. 10 | 11 | # EN: The Singleton class defines the `instance` method that lets clients 12 | # access the unique singleton instance. 13 | # 14 | # RU: Класс Одиночка предоставляет метод instance, который позволяет 15 | # клиентам получить доступ к уникальному экземпляру одиночки. 16 | class Singleton 17 | @instance = new 18 | 19 | private_class_method :new 20 | 21 | # EN: The static method that controls the access to the singleton 22 | # instance. 23 | # 24 | # This implementation let you subclass the Singleton class while keeping 25 | # just one instance of each subclass around. 26 | # 27 | # RU: Статический метод, управляющий доступом к экземпляру одиночки. 28 | # 29 | # Эта реализация позволяет вам расширять класс Одиночки, сохраняя повсюду 30 | # только один экземпляр каждого подкласса. 31 | def self.instance 32 | @instance 33 | end 34 | 35 | # EN: Finally, any singleton should define some business logic, which can 36 | # be executed on its instance. 37 | # 38 | # RU: Наконец, любой одиночка должен содержать некоторую бизнес-логику, 39 | # которая может быть выполнена на его экземпляре. 40 | def some_business_logic 41 | # ... 42 | end 43 | end 44 | 45 | # EN: The client code. 46 | # 47 | # RU: Клиентский код. 48 | 49 | s1 = Singleton.instance 50 | s2 = Singleton.instance 51 | 52 | if s1.equal?(s2) 53 | print 'Singleton works, both variables contain the same instance.' 54 | else 55 | print 'Singleton failed, variables contain different instances.' 56 | end 57 | -------------------------------------------------------------------------------- /src/singleton/conceptual/thread_safe/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Singleton Design Pattern 2 | # 3 | # Intent: Lets you ensure that a class has only one instance, while providing a 4 | # global access point to this instance. 5 | # 6 | # RU: Паттерн Одиночка 7 | # 8 | # Назначение: Гарантирует, что у класса есть только один экземпляр, и 9 | # предоставляет к нему глобальную точку доступа. 10 | 11 | # EN: The Singleton class defines the `instance` method that lets clients 12 | # access the unique singleton instance. 13 | # 14 | # RU: Класс Одиночка предоставляет метод instance, который позволяет 15 | # клиентам получить доступ к уникальному экземпляру одиночки. 16 | class Singleton 17 | attr_reader :value 18 | 19 | @instance_mutex = Mutex.new 20 | 21 | private_class_method :new 22 | 23 | def initialize(value) 24 | @value = value 25 | end 26 | 27 | # EN: The static method that controls the access to the singleton 28 | # instance. 29 | # 30 | # This implementation let you subclass the Singleton class while keeping 31 | # just one instance of each subclass around. 32 | # 33 | # RU: Статический метод, управляющий доступом к экземпляру одиночки. 34 | # 35 | # Эта реализация позволяет вам расширять класс Одиночки, сохраняя повсюду 36 | # только один экземпляр каждого подкласса. 37 | def self.instance(value) 38 | return @instance if @instance 39 | 40 | @instance_mutex.synchronize do 41 | @instance ||= new(value) 42 | end 43 | 44 | @instance 45 | end 46 | 47 | # EN: Finally, any singleton should define some business logic, which can 48 | # be executed on its instance. 49 | # 50 | # RU: Наконец, любой одиночка должен содержать некоторую бизнес-логику, 51 | # которая может быть выполнена на его экземпляре. 52 | def some_business_logic 53 | # ... 54 | end 55 | end 56 | 57 | # @param [String] value 58 | def test_singleton(value) 59 | singleton = Singleton.instance(value) 60 | puts singleton.value 61 | end 62 | 63 | # EN: The client code. 64 | # 65 | # RU: Клиентский код. 66 | 67 | puts "If you see the same value, then singleton was reused (yay!)\n"\ 68 | "If you see different values, then 2 singletons were created (booo!!)\n\n"\ 69 | "RESULT:\n\n" 70 | 71 | process1 = Thread.new { test_singleton('FOO') } 72 | process2 = Thread.new { test_singleton('BAR') } 73 | process1.join 74 | process2.join 75 | -------------------------------------------------------------------------------- /src/adapter/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Adapter Design Pattern 2 | # 3 | # Intent: Provides a unified interface that allows objects with incompatible 4 | # interfaces to collaborate. 5 | # 6 | # RU: Паттерн Адаптер 7 | # 8 | # Назначение: Позволяет объектам с несовместимыми интерфейсами работать вместе. 9 | 10 | # EN: The Target defines the domain-specific interface used by the client 11 | # code. 12 | # 13 | # RU: Целевой класс объявляет интерфейс, с которым может работать клиентский 14 | # код. 15 | class Target 16 | # @return [String] 17 | def request 18 | 'Target: The default target\'s behavior.' 19 | end 20 | end 21 | 22 | # EN: The Adaptee contains some useful behavior, but its interface is 23 | # incompatible with the existing client code. The Adaptee needs some 24 | # adaptation before the client code can use it. 25 | # 26 | # RU: Адаптируемый класс содержит некоторое полезное поведение, но его 27 | # интерфейс несовместим с существующим клиентским кодом. Адаптируемый класс 28 | # нуждается в некоторой доработке, прежде чем клиентский код сможет его 29 | # использовать. 30 | class Adaptee 31 | # @return [String] 32 | def specific_request 33 | '.eetpadA eht fo roivaheb laicepS' 34 | end 35 | end 36 | 37 | # EN: The Adapter makes the Adaptee's interface compatible with the Target's 38 | # interface. 39 | # 40 | # RU: Адаптер делает интерфейс Адаптируемого класса совместимым с целевым 41 | # интерфейсом. 42 | class Adapter < Target 43 | # @param [Adaptee] adaptee 44 | def initialize(adaptee) 45 | @adaptee = adaptee 46 | end 47 | 48 | def request 49 | "Adapter: (TRANSLATED) #{@adaptee.specific_request.reverse!}" 50 | end 51 | end 52 | 53 | # EN: The client code supports all classes that follow the Target interface. 54 | # 55 | # RU: Клиентский код поддерживает все классы, использующие интерфейс Target. 56 | # 57 | # @param [Target] target 58 | def client_code(target) 59 | print target.request 60 | end 61 | 62 | puts 'Client: I can work just fine with the Target objects:' 63 | target = Target.new 64 | client_code(target) 65 | puts "\n\n" 66 | 67 | adaptee = Adaptee.new 68 | puts 'Client: The Adaptee class has a weird interface. See, I don\'t understand it:' 69 | puts "Adaptee: #{adaptee.specific_request}" 70 | puts "\n" 71 | 72 | puts 'Client: But I can work with it via the Adapter:' 73 | adapter = Adapter.new(adaptee) 74 | client_code(adapter) 75 | -------------------------------------------------------------------------------- /src/bridge/real_world/main.rb: -------------------------------------------------------------------------------- 1 | # EN: This is a real life example of a bridge pattern usage. 2 | # The main concept is that from the Abstraction (Device) and Implementation 3 | # (BasicRemote) different teams can create new subclasses without interfering 4 | # with each other. 5 | # 6 | # RU: Это настоящий пример использования моста.Основная концепция заключается в 7 | # том, что из абстракции (Device) и реализации (BasicRemote) различные команды 8 | # могут создавать новые подклассы, не мешая друг другу. 9 | # 10 | class Device 11 | attr_accessor :channel, :volume 12 | 13 | def initialize 14 | @enabled = false 15 | @volume = 30 16 | @channel = 1 17 | end 18 | 19 | def enabled? 20 | @enabled 21 | end 22 | 23 | def enable 24 | @enabled = true 25 | end 26 | 27 | def disable 28 | @enabled = false 29 | end 30 | 31 | def print_status 32 | raise NotImplementedError, 33 | "#{self.class} has not implemented method '#{__method__}'" 34 | end 35 | end 36 | 37 | class Radio < Device 38 | def print_status 39 | puts <<~TEXT 40 | ------------------------------------ 41 | | I'm radio. 42 | | I'm #{enabled? ? 'enabled' : 'disabled'} 43 | | Current volume is #{volume}% 44 | | Current channel is #{channel} 45 | ------------------------------------\n 46 | TEXT 47 | end 48 | end 49 | 50 | class Tv < Device 51 | def print_status 52 | puts <<~TEXT 53 | ------------------------------------ 54 | | I'm a TV. 55 | | I'm #{enabled? ? 'enabled' : 'disabled'} 56 | | Current volume is #{volume}% 57 | | Current channel is #{channel} 58 | ------------------------------------ 59 | TEXT 60 | end 61 | end 62 | 63 | class BasicRemote 64 | def initialize(device) 65 | @device = device 66 | end 67 | 68 | def power 69 | puts 'Remote: power toggle' 70 | @device.enabled? ? @device.disable : @device.enable 71 | end 72 | 73 | def volume_down 74 | @device.volume -= 10 75 | end 76 | 77 | def volume_up 78 | @device.volume += 10 79 | end 80 | 81 | def channel_down 82 | @device.channel -= 1 83 | end 84 | 85 | def channel_up 86 | @device.channel += 1 87 | end 88 | end 89 | 90 | class AdvancedRemote < BasicRemote 91 | def mute 92 | @device.volume = 0 93 | end 94 | end 95 | 96 | # EN: Demo test code 97 | # 98 | # RU: Демо -тестовый код 99 | # 100 | def test_device(device) 101 | puts 'Tests with basic remote.' 102 | basic_remote = BasicRemote.new(device) 103 | basic_remote.power 104 | device.print_status 105 | 106 | puts 'Tests with advanced remote.' 107 | advanced_remote = AdvancedRemote.new(device) 108 | advanced_remote.power 109 | advanced_remote.mute 110 | device.print_status 111 | end 112 | 113 | test_device(Radio.new) 114 | test_device(Tv.new) 115 | -------------------------------------------------------------------------------- /src/iterator/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Iterator Design Pattern 2 | # 3 | # Intent: Lets you traverse elements of a collection without exposing its 4 | # underlying representation (list, stack, tree, etc.). 5 | # 6 | # RU: Паттерн Итератор 7 | # 8 | # Назначение: Даёт возможность последовательно обходить элементы составных 9 | # объектов, не раскрывая их внутреннего представления. 10 | 11 | class AlphabeticalOrderIterator 12 | # EN: In Ruby, the Enumerable mixin provides classes with several traversal 13 | # and searching methods, and with the ability to sort. The class must provide 14 | # a method each, which yields successive members of the collection. 15 | # 16 | # RU: Примесь Enumerable в Ruby предоставляет классы методами обхода, поиска и 17 | # сортировки значений. Класс, реализующий Enumerable должен определить метод 18 | # `each`, который возвращает (в yield) последовательно элементы коллекции. 19 | include Enumerable 20 | 21 | # EN: This attribute indicates the traversal direction. 22 | # 23 | # RU: Этот атрибут указывает направление обхода. 24 | # @return [Boolean] 25 | attr_accessor :reverse 26 | private :reverse 27 | 28 | # @return [Array] 29 | attr_accessor :collection 30 | private :collection 31 | 32 | # @param [Array] collection 33 | # @param [Boolean] reverse 34 | def initialize(collection, reverse: false) 35 | @collection = collection 36 | @reverse = reverse 37 | end 38 | 39 | def each(&block) 40 | return @collection.reverse.each(&block) if reverse 41 | 42 | @collection.each(&block) 43 | end 44 | end 45 | 46 | class WordsCollection 47 | # @return [Array] 48 | attr_accessor :collection 49 | private :collection 50 | 51 | def initialize(collection = []) 52 | @collection = collection 53 | end 54 | 55 | # EN: The `iterator` method returns the iterator object itself, by default 56 | # we return the iterator in ascending order. 57 | # 58 | # RU: Метод iterator возвращает объект итератора, по умолчанию мы 59 | # возвращаем итератор с сортировкой по возрастанию. 60 | # 61 | # @return [AlphabeticalOrderIterator] 62 | def iterator 63 | AlphabeticalOrderIterator.new(@collection) 64 | end 65 | 66 | # @return [AlphabeticalOrderIterator] 67 | def reverse_iterator 68 | AlphabeticalOrderIterator.new(@collection, reverse: true) 69 | end 70 | 71 | # @param [String] item 72 | def add_item(item) 73 | @collection << item 74 | end 75 | end 76 | 77 | # EN: The client code may or may not know about the Concrete Iterator or 78 | # Collection classes, depending on the level of indirection you want to keep 79 | # in your program. 80 | # 81 | # RU: Клиентский код может знать или не знать о Конкретном Итераторе или 82 | # классах Коллекций, в зависимости от уровня косвенности, который вы хотите 83 | # сохранить в своей программе. 84 | collection = WordsCollection.new 85 | collection.add_item('First') 86 | collection.add_item('Second') 87 | collection.add_item('Third') 88 | 89 | puts 'Straight traversal:' 90 | collection.iterator.each { |item| puts item } 91 | puts "\n" 92 | 93 | puts 'Reverse traversal:' 94 | collection.reverse_iterator.each { |item| puts item } 95 | -------------------------------------------------------------------------------- /src/prototype/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Prototype Design Pattern 2 | # 3 | # Intent: Lets you copy existing objects without making your code dependent on 4 | # their classes. 5 | # 6 | # RU: Паттерн Прототип 7 | # 8 | # Назначение: Позволяет копировать объекты, не вдаваясь в подробности их 9 | # реализации. 10 | 11 | # EN: The example class that has cloning ability. We'll see how the values of 12 | # field with different types will be cloned. 13 | # 14 | # RU: Пример класса, имеющего возможность клонирования. Мы посмотрим как 15 | # происходит клонирование значений полей разных типов. 16 | class Prototype 17 | attr_accessor :primitive, :component, :circular_reference 18 | 19 | def initialize 20 | @primitive = nil 21 | @component = nil 22 | @circular_reference = nil 23 | end 24 | 25 | # @return [Prototype] 26 | def clone 27 | @component = deep_copy(@component) 28 | 29 | # EN: Cloning an object that has a nested object with backreference 30 | # requires special treatment. After the cloning is completed, the nested 31 | # object should point to the cloned object, instead of the original 32 | # object. 33 | # 34 | # RU: Клонирование объекта, который имеет вложенный объект с обратной 35 | # ссылкой, требует специального подхода. После завершения клонирования 36 | # вложенный объект должен указывать на клонированный объект, а не на 37 | # исходный объект. 38 | @circular_reference = deep_copy(@circular_reference) 39 | @circular_reference.prototype = self 40 | deep_copy(self) 41 | end 42 | 43 | # EN: deep_copy is the usual Marshalling hack to make a deep copy. But it's 44 | # rather slow and inefficient, therefore, in real applications, use a special 45 | # gem. 46 | # 47 | # RU: Нередко метод deep_copy использует хак «маршалинг», чтобы создать 48 | # глубокую копию объекта. Однако это медленное и неэффективно, поэтому в 49 | # реальных приложениях используйте для этой задачи соответствующий пакет. 50 | # 51 | # @param [Object] object 52 | private def deep_copy(object) 53 | Marshal.load(Marshal.dump(object)) 54 | end 55 | end 56 | 57 | class ComponentWithBackReference 58 | attr_accessor :prototype 59 | 60 | # @param [Prototype] prototype 61 | def initialize(prototype) 62 | @prototype = prototype 63 | end 64 | end 65 | 66 | # EN: The client code. 67 | # 68 | # RU: Клиентский код. 69 | p1 = Prototype.new 70 | p1.primitive = 245 71 | p1.component = Time.now 72 | p1.circular_reference = ComponentWithBackReference.new(p1) 73 | 74 | p2 = p1.clone 75 | 76 | if p1.primitive == p2.primitive 77 | puts 'Primitive field values have been carried over to a clone. Yay!' 78 | else 79 | puts 'Primitive field values have not been copied. Booo!' 80 | end 81 | 82 | if p1.component.equal?(p2.component) 83 | puts 'Simple component has not been cloned. Booo!' 84 | else 85 | puts 'Simple component has been cloned. Yay!' 86 | end 87 | 88 | if p1.circular_reference.equal?(p2.circular_reference) 89 | puts 'Component with back reference has not been cloned. Booo!' 90 | else 91 | puts 'Component with back reference has been cloned. Yay!' 92 | end 93 | 94 | if p1.circular_reference.prototype.equal?(p2.circular_reference.prototype) 95 | print 'Component with back reference is linked to original object. Booo!' 96 | else 97 | print 'Component with back reference is linked to the clone. Yay!' 98 | end 99 | -------------------------------------------------------------------------------- /src/mediator/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Mediator Design Pattern 2 | # 3 | # Intent: Lets you reduce chaotic dependencies between objects. The pattern 4 | # restricts direct communications between the objects and forces them to 5 | # collaborate only via a mediator object. 6 | # 7 | # RU: Паттерн Посредник 8 | # 9 | # Назначение: Позволяет уменьшить связанность множества классов между собой, 10 | # благодаря перемещению этих связей в один класс-посредник. 11 | 12 | # EN: The Mediator interface declares a method used by components to notify 13 | # the mediator about various events. The Mediator may react to these events 14 | # and pass the execution to other components. 15 | # 16 | # RU: Интерфейс Посредника предоставляет метод, используемый компонентами для 17 | # уведомления посредника о различных событиях. Посредник может реагировать на 18 | # эти события и передавать исполнение другим компонентам. 19 | # 20 | # @abstract 21 | class Mediator 22 | # @abstract 23 | # 24 | # @param [Object] sender 25 | # @param [String] event 26 | def notify(_sender, _event) 27 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 28 | end 29 | end 30 | 31 | class ConcreteMediator < Mediator 32 | # @param [Component1] component1 33 | # @param [Component2] component2 34 | def initialize(component1, component2) 35 | @component1 = component1 36 | @component1.mediator = self 37 | @component2 = component2 38 | @component2.mediator = self 39 | end 40 | 41 | # @param [Object] sender 42 | # @param [String] event 43 | def notify(_sender, event) 44 | if event == 'A' 45 | puts 'Mediator reacts on A and triggers following operations:' 46 | @component2.do_c 47 | elsif event == 'D' 48 | puts 'Mediator reacts on D and triggers following operations:' 49 | @component1.do_b 50 | @component2.do_c 51 | end 52 | end 53 | end 54 | 55 | # EN: The Base Component provides the basic functionality of storing a 56 | # mediator's instance inside component objects. 57 | # 58 | # RU: Базовый Компонент обеспечивает базовую функциональность хранения 59 | # экземпляра посредника внутри объектов компонентов. 60 | class BaseComponent 61 | # @return [Mediator] 62 | attr_accessor :mediator 63 | 64 | # @param [Mediator] mediator 65 | def initialize(mediator = nil) 66 | @mediator = mediator 67 | end 68 | end 69 | 70 | # EN: Concrete Components implement various functionality. They don't depend on 71 | # other components. They also don't depend on any concrete mediator classes. 72 | # 73 | # RU: Конкретные Компоненты реализуют различную функциональность. Они не зависят 74 | # от других компонентов. Они также не зависят от каких-либо конкретных классов 75 | # посредников. 76 | class Component1 < BaseComponent 77 | def do_a 78 | puts 'Component 1 does A.' 79 | @mediator.notify(self, 'A') 80 | end 81 | 82 | def do_b 83 | puts 'Component 1 does B.' 84 | @mediator.notify(self, 'B') 85 | end 86 | end 87 | 88 | class Component2 < BaseComponent 89 | def do_c 90 | puts 'Component 2 does C.' 91 | @mediator.notify(self, 'C') 92 | end 93 | 94 | def do_d 95 | puts 'Component 2 does D.' 96 | @mediator.notify(self, 'D') 97 | end 98 | end 99 | 100 | # EN: The client code. 101 | # 102 | # RU: Клиентский код. 103 | c1 = Component1.new 104 | c2 = Component2.new 105 | ConcreteMediator.new(c1, c2) 106 | 107 | puts 'Client triggers operation A.' 108 | c1.do_a 109 | 110 | puts "\n" 111 | 112 | puts 'Client triggers operation D.' 113 | c2.do_d 114 | -------------------------------------------------------------------------------- /src/adapter/real_world/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Imagine you work on an application that checks if an automobile was speeding 2 | # and the whole application uses the metric system. 3 | # Your boss tells you that you will be integrating with an external application 4 | # that uses the imperial system. 5 | # 6 | # Ru: Представьте, что вы работаете над приложением, которое проверяет, превысил 7 | # ли автомобиль скорость, и все приложение использует метрическую систему. 8 | # Ваш начальник сообщает вам, что вы будете интегрироваться с внешним 9 | # приложением, использующим имперскую систему. 10 | # 11 | # 12 | # EN: This is the Speed class, it contains the speed value and the unit 13 | # 14 | # RU: Это класс скорости, он содержит значение скорости и единицу 15 | # 16 | class Speed 17 | include Comparable 18 | attr_reader :value 19 | 20 | def initialize(value) 21 | @value = value 22 | end 23 | 24 | def unit 25 | raise NotImplementedError, 26 | "#{self.class} has not implemented method '#{__method__}'" 27 | end 28 | 29 | # EN: We raise an error if we try to compare speeds with different units 30 | # 31 | # RU: Мы поднимаем ошибку, если попытаемся сравнивать скорости с разными единицами 32 | # 33 | def <=>(other) 34 | raise 'The speeds have different units' if unit != other.unit 35 | 36 | value <=> other.value 37 | end 38 | end 39 | 40 | # EN: This is the class that is most used in the internal system. 41 | # 42 | # RU: Это класс, который наиболее используется во внутренней системе. 43 | # 44 | class KilometersSpeed < Speed 45 | def unit 46 | 'km/h' 47 | end 48 | end 49 | 50 | # EN: This is the type of data you will receive from the external API 51 | # 52 | # RU: Это тот тип данных, которые вы получите от внешнего API 53 | # 54 | class MilesSpeed < Speed 55 | def unit 56 | 'mi/h' 57 | end 58 | end 59 | 60 | # EN: This class checks if the speed is above or bellow the maximum limit 61 | # 62 | # RU: Этот класс проверяет, если скорость выше или ниже максимального предела 63 | # 64 | class KilometersSpeedLimit 65 | MAX_LIMIT = KilometersSpeed.new(100) 66 | 67 | def self.speeding?(speed) 68 | if speed > MAX_LIMIT 69 | puts "(#{speed.value}#{speed.unit}) You are speeding" 70 | else 71 | puts "(#{speed.value}#{speed.unit}) You are bellow the max limit" 72 | end 73 | end 74 | end 75 | 76 | # EN: This is the adaptor that converts the speed from miles per hour to 77 | # kilometers per hour 78 | # 79 | # RU: Это адаптер, который преобразует скорость с миль в час в километры в час 80 | # 81 | class KilometersAdaptor < MilesSpeed 82 | def initialize(speed) 83 | @value = speed.value * 1.61 84 | end 85 | 86 | def unit 87 | 'km/h' 88 | end 89 | end 90 | 91 | # EN: This is an example of usage in a real application. 92 | # These are the objects you would have inside your application. 93 | # 94 | # RU: Это пример использования в реальном приложении. 95 | # Это те объекты, которые вы бы имели в вашем приложении. 96 | # 97 | slow_km_speed = KilometersSpeed.new(90) 98 | fast_km_speed = KilometersSpeed.new(110) 99 | 100 | KilometersSpeedLimit.speeding?(slow_km_speed) 101 | KilometersSpeedLimit.speeding?(fast_km_speed) 102 | 103 | # EN: These would be the objects you generate from the data you received from 104 | # the external API. 105 | # 106 | # RU: Это будут объекты, которые вы генерируете из данных, полученных от 107 | # внешнего API. 108 | # 109 | slow_mi_speed = MilesSpeed.new(50) 110 | fast_mi_speed = MilesSpeed.new(80) 111 | 112 | slow_mi_adaptor = KilometersAdaptor.new(slow_mi_speed) 113 | fast_mi_adaptor = KilometersAdaptor.new(fast_mi_speed) 114 | 115 | KilometersSpeedLimit.speeding?(slow_mi_adaptor) 116 | KilometersSpeedLimit.speeding?(fast_mi_adaptor) 117 | -------------------------------------------------------------------------------- /src/state/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: State Design Pattern 2 | # 3 | # Intent: Lets an object alter its behavior when its internal state changes. It 4 | # appears as if the object changed its class. 5 | # 6 | # RU: Паттерн Состояние 7 | # 8 | # Назначение: Позволяет объектам менять поведение в зависимости от своего 9 | # состояния. Извне создаётся впечатление, что изменился класс объекта. 10 | 11 | # EN: The Context defines the interface of interest to clients. It also 12 | # maintains a reference to an instance of a State subclass, which represents 13 | # the current state of the Context. 14 | # 15 | # RU: Контекст определяет интерфейс, представляющий интерес для клиентов. Он 16 | # также хранит ссылку на экземпляр подкласса Состояния, который отображает 17 | # текущее состояние Контекста. 18 | # 19 | # @abstract 20 | class Context 21 | # EN: A reference to the current state of the Context. 22 | # 23 | # RU: Ссылка на текущее состояние Контекста. 24 | # @return [State] 25 | attr_accessor :state 26 | private :state 27 | 28 | # @param [State] state 29 | def initialize(state) 30 | transition_to(state) 31 | end 32 | 33 | # EN: The Context allows changing the State object at runtime. 34 | # 35 | # RU: Контекст позволяет изменять объект Состояния во время выполнения. 36 | # 37 | # @param [State] state 38 | def transition_to(state) 39 | puts "Context: Transition to #{state.class}" 40 | @state = state 41 | @state.context = self 42 | end 43 | 44 | # EN: The Context delegates part of its behavior to the current State object. 45 | # 46 | # RU: Контекст делегирует часть своего поведения текущему объекту Состояния. 47 | 48 | def request1 49 | @state.handle1 50 | end 51 | 52 | def request2 53 | @state.handle2 54 | end 55 | end 56 | 57 | # EN: The base State class declares methods that all Concrete State should 58 | # implement and also provides a backreference to the Context object, 59 | # associated with the State. This backreference can be used by States to 60 | # transition the Context to another State. 61 | # 62 | # RU: Базовый класс Состояния объявляет методы, которые должны реализовать все 63 | # Конкретные Состояния, а также предоставляет обратную ссылку на объект 64 | # Контекст, связанный с Состоянием. Эта обратная ссылка может использоваться 65 | # Состояниями для передачи Контекста другому Состоянию. 66 | # 67 | # @abstract 68 | class State 69 | attr_accessor :context 70 | 71 | # @abstract 72 | def handle1 73 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 74 | end 75 | 76 | # @abstract 77 | def handle2 78 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 79 | end 80 | end 81 | 82 | # EN: Concrete States implement various behaviors, associated with a state of the 83 | # Context. 84 | # 85 | # RU: Конкретные Состояния реализуют различные модели поведения, связанные с 86 | # состоянием Контекста. 87 | 88 | class ConcreteStateA < State 89 | def handle1 90 | puts 'ConcreteStateA handles request1.' 91 | puts 'ConcreteStateA wants to change the state of the context.' 92 | @context.transition_to(ConcreteStateB.new) 93 | end 94 | 95 | def handle2 96 | puts 'ConcreteStateA handles request2.' 97 | end 98 | end 99 | 100 | class ConcreteStateB < State 101 | def handle1 102 | puts 'ConcreteStateB handles request1.' 103 | end 104 | 105 | def handle2 106 | puts 'ConcreteStateB handles request2.' 107 | puts 'ConcreteStateB wants to change the state of the context.' 108 | @context.transition_to(ConcreteStateA.new) 109 | end 110 | end 111 | 112 | # EN: The client code. 113 | # 114 | # RU: Клиентский код. 115 | 116 | context = Context.new(ConcreteStateA.new) 117 | context.request1 118 | context.request2 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns in Ruby 2 | 3 | This repository is part of the [Refactoring.Guru](https://refactoring.guru/design-patterns) project. 4 | 5 | It contains Ruby examples for all classic GoF design patterns. 6 | 7 | Each pattern includes two examples: 8 | 9 | - [x] **Conceptual** examples show the internal structure of patterns, including detailed comments. 10 | 11 | - [ ] **RealWorld** examples show how patterns can be used in real-world Ruby applications. 12 | 13 | 14 | ## Requirements 15 | 16 | These examples require Ruby 3.2 and newer, although they can be easily replicated in older versions of Ruby. 17 | 18 | This version provides explicit argument and return type declarations, which help to understand better some patterns' features that are not very obvious in dynamically typed language. 19 | 20 | All examples can be launched via the command line, using the Ruby executable as follows: 21 | 22 | ``` 23 | ruby src/Path-to-example/main.rb 24 | ``` 25 | 26 | For the best experience, I recommend working with examples with these IDEs: 27 | 28 | - [RubyMine](https://www.jetbrains.com/ruby/) 29 | - [Visual Studio Code](https://code.visualstudio.com/) with the [Ruby extension](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) 30 | 31 | 32 | ## FAQ 33 | 34 | #### 1. What is the _Client Code_? 35 | 36 | _Client_ means _client of classes, defined as part of a pattern_, which is merely a caller of the given methods or a user of the given classes. In other words, it's the part of your application's code that uses the pattern's classes. 37 | 38 | #### 2. I don't understand the roles you're referring to in RealWorld examples. 39 | 40 | Take a look at the conceptual example first. There you'll find detailed descriptions of each class in a pattern, its role, and connection to other classes. 41 | 42 | 43 | ## Contributor's Guide 44 | 45 | I appreciate any help, whether it's a simple fix of a typo or a whole new example. Just [make a fork](https://help.github.com/articles/fork-a-repo/), make your change and submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). 46 | 47 | Here's a style guide which might help you to keep your changes consistent with the rest of the project's code: 48 | 49 | 1. All code should match the [Ruby Style Guide (community-driven)](https://github.com/rubocop-hq/ruby-style-guide) 50 | 51 | 2. Try to hard-wrap the code at 80th's character. It helps to list the code on the website without scrollbars. 52 | 53 | 3. Aim to put all code within one file. Yes, I realize that it's not how it supposed to be done in production. However, it helps people to understand examples better, since all code fits into one screen. 54 | 55 | 4. Comments may or may not have language tags in them, such as this: 56 | 57 | ```ruby 58 | # EN: All products families have the same varieties (MacOS/Windows). 59 | # 60 | # This is a MacOS variant of a button. 61 | # 62 | # RU: Все семейства продуктов имеют одни и те же вариации (MacOS/Windows). 63 | # 64 | # Это вариант кнопки под MacOS. 65 | ``` 66 | 67 | This notation helps to keep the code in one place while allowing the website to generates separate versions of examples for all listed languages. Don't be scared and ignore the non-English part of such comments. If you want to change something in a comment like this, just do it. Even if you do it wrong, we'll tell you how to fix it during the Pull Request. 68 | 69 | 70 | ## License 71 | 72 | This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. 73 | 74 | Creative Commons License 75 | 76 | 77 | ## Credits 78 | 79 | Authors: Alexey Pyltsyn ([@lex111](https://github.com/lex111)) and Alexander Shvets ([@neochief](https://github.com/neochief)) 80 | -------------------------------------------------------------------------------- /src/strategy/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Strategy Design Pattern 2 | # 3 | # Intent: Lets you define a family of algorithms, put each of them into a separate 4 | # class, and make their objects interchangeable. 5 | # 6 | # RU: Паттерн Стратегия 7 | # 8 | # Назначение: Определяет семейство схожих алгоритмов и помещает каждый из них в 9 | # собственный класс, после чего алгоритмы можно взаимозаменять прямо во время 10 | # исполнения программы. 11 | 12 | # EN: The Context defines the interface of interest to clients. 13 | # 14 | # RU: Контекст определяет интерфейс, представляющий интерес для клиентов. 15 | class Context 16 | # EN: The Context maintains a reference to one of the Strategy objects. 17 | # The Context does not know the concrete class of a strategy. It should 18 | # work with all strategies via the Strategy interface. 19 | # 20 | # RU: Контекст хранит ссылку на один из объектов Стратегии. Контекст не 21 | # знает конкретного класса стратегии. Он должен работать со всеми 22 | # стратегиями через интерфейс Стратегии. 23 | # @return [Strategy] 24 | attr_writer :strategy 25 | 26 | # EN: Usually, the Context accepts a strategy through the constructor, but 27 | # also provides a setter to change it at runtime. 28 | # 29 | # RU: Обычно Контекст принимает стратегию через конструктор, а также 30 | # предоставляет сеттер для её изменения во время выполнения. 31 | # 32 | # @param [Strategy] strategy 33 | def initialize(strategy) 34 | @strategy = strategy 35 | end 36 | 37 | # EN: Usually, the Context allows replacing a Strategy object at runtime. 38 | # 39 | # RU: Обычно Контекст позволяет заменить объект Стратегии во время 40 | # выполнения. 41 | # 42 | # @param [Strategy] strategy 43 | def strategy=(strategy) 44 | @strategy = strategy 45 | end 46 | 47 | # EN: The Context delegates some work to the Strategy object instead of 48 | # implementing multiple versions of the algorithm on its own. 49 | # 50 | # RU: Вместо того, чтобы самостоятельно реализовывать множественные версии 51 | # алгоритма, Контекст делегирует некоторую работу объекту Стратегии. 52 | def do_some_business_logic 53 | # ... 54 | 55 | puts 'Context: Sorting data using the strategy (not sure how it\'ll do it)' 56 | result = @strategy.do_algorithm(%w[a b c d e]) 57 | print result.join(',') 58 | 59 | # ... 60 | end 61 | end 62 | 63 | # EN: The Strategy interface declares operations common to all supported 64 | # versions of some algorithm. 65 | # 66 | # The Context uses this interface to call the algorithm defined by Concrete 67 | # Strategies. 68 | # 69 | # RU: Интерфейс Стратегии объявляет операции, общие для всех поддерживаемых 70 | # версий некоторого алгоритма. 71 | # 72 | # Контекст использует этот интерфейс для вызова алгоритма, определённого 73 | # Конкретными Стратегиями. 74 | # 75 | # @abstract 76 | class Strategy 77 | # @abstract 78 | # 79 | # @param [Array] data 80 | def do_algorithm(_data) 81 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 82 | end 83 | end 84 | 85 | # EN: Concrete Strategies implement the algorithm while following the base 86 | # Strategy interface. The interface makes them interchangeable in the Context. 87 | # 88 | # RU: Конкретные Стратегии реализуют алгоритм, следуя базовому интерфейсу 89 | # Стратегии. Этот интерфейс делает их взаимозаменяемыми в Контексте. 90 | 91 | class ConcreteStrategyA < Strategy 92 | # @param [Array] data 93 | # 94 | # @return [Array] 95 | def do_algorithm(data) 96 | data.sort 97 | end 98 | end 99 | 100 | class ConcreteStrategyB < Strategy 101 | # @param [Array] data 102 | # 103 | # @return [Array] 104 | def do_algorithm(data) 105 | data.sort.reverse 106 | end 107 | end 108 | 109 | # EN: The client code picks a concrete strategy and passes it to the 110 | # context. The client should be aware of the differences between strategies 111 | # in order to make the right choice. 112 | # 113 | # RU: Клиентский код выбирает конкретную стратегию и передаёт её в контекст. 114 | # Клиент должен знать о различиях между стратегиями, чтобы сделать 115 | # правильный выбор. 116 | 117 | context = Context.new(ConcreteStrategyA.new) 118 | puts 'Client: Strategy is set to normal sorting.' 119 | context.do_some_business_logic 120 | puts "\n\n" 121 | 122 | puts 'Client: Strategy is set to reverse sorting.' 123 | context.strategy = ConcreteStrategyB.new 124 | context.do_some_business_logic 125 | -------------------------------------------------------------------------------- /src/abstract_factory/real_world/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Abstract Factory assumes that you have several families of products, 2 | # structured into separate class hierarchies (Button/Checkbox). All products of 3 | # the same family have the common interface. 4 | # 5 | # This is the common interface for buttons family. 6 | # 7 | # RU: Паттерн предполагает, что у вас есть несколько семейств продуктов, 8 | # находящихся в отдельных иерархиях классов (Button/Checkbox). Продукты одного 9 | # семейства должны иметь общий интерфейс. 10 | # 11 | # Это — общий интерфейс для семейства продуктов кнопок. 12 | # 13 | class Button 14 | def draw 15 | raise NotImplementedError, 16 | "#{self.class} has not implemented method '#{__method__}'" 17 | end 18 | end 19 | 20 | # EN: All products families have the same varieties (MacOS/Windows). 21 | # 22 | # This is a MacOS variant of a button. 23 | # 24 | # RU: Все семейства продуктов имеют одни и те же вариации (MacOS/Windows). 25 | # 26 | # Это вариант кнопки под MacOS. 27 | # 28 | class MacOSButton < Button 29 | def draw 30 | puts 'MacOSButton has been drawn' 31 | end 32 | end 33 | 34 | # EN: This is a Windows variant of a button. 35 | # 36 | # RU: Это вариант кнопки под Windows. 37 | # 38 | class WindowsButton < Button 39 | def draw 40 | puts 'WindowsButton has been drawn' 41 | end 42 | end 43 | 44 | # EN: Checkboxes is the second product family. It has the same variants as 45 | # buttons. 46 | # 47 | # RU: Чекбоксы — это второе семейство продуктов. Оно имеет те же вариации, что 48 | # и кнопки. 49 | # 50 | class Checkbox 51 | def draw 52 | raise NotImplementedError, 53 | "#{self.class} has not implemented method '#{__method__}'" 54 | end 55 | end 56 | 57 | # EN: This is a MacOS variant of a checkbox. 58 | # 59 | # RU: Вариация чекбокса под MacOS. 60 | # 61 | class MacOSCheckbox < Checkbox 62 | def draw 63 | puts 'MacOSCheckbox has been drawn' 64 | end 65 | end 66 | 67 | # EN: This is a Windows variant of a checkbox. 68 | # 69 | # RU: Вариация чекбокса под Windows. 70 | # 71 | class WindowsCheckbox < Checkbox 72 | def draw 73 | puts 'WindowsCheckbox has been drawn' 74 | end 75 | end 76 | 77 | # EN: This is an example of abstract factory. 78 | # 79 | # RU: Это пример абстрактной фабрики. 80 | # 81 | class GUIFactory 82 | def create_button 83 | raise NotImplementedError, 84 | "#{self.class} has not implemented method '#{__method__}'" 85 | end 86 | 87 | def create_checkbox 88 | raise NotImplementedError, 89 | "#{self.class} has not implemented method '#{__method__}'" 90 | end 91 | end 92 | 93 | # EN: This is a MacOS concrete factory 94 | # 95 | # RU: Это бетонный завод MacOS 96 | # 97 | class MacOSFactory < GUIFactory 98 | def create_button 99 | MacOSButton.new 100 | end 101 | 102 | def create_checkbox 103 | MacOSCheckbox.new 104 | end 105 | end 106 | 107 | # EN: This is a Windwows concrete factory 108 | # 109 | # RU: Это бетонный завод Windwows 110 | # 111 | class WindowsFactory < GUIFactory 112 | def create_button 113 | WindowsButton.new 114 | end 115 | 116 | def create_checkbox 117 | WindowsCheckbox.new 118 | end 119 | end 120 | 121 | # EN: Factory users don't care which concrete factory they use since they work 122 | # with factories and products through abstract interfaces. 123 | # 124 | # RU: Код, использующий фабрику, не волнует с какой конкретно фабрикой он 125 | # работает. Все получатели продуктов работают с продуктами через абстрактный 126 | # интерфейс. 127 | # 128 | class Application 129 | def initialize(factory) 130 | @button = factory.create_button 131 | @checkbox = factory.create_checkbox 132 | end 133 | 134 | def draw 135 | @button.draw 136 | @checkbox.draw 137 | end 138 | end 139 | 140 | # EN: This is an example of usage in a real application. 141 | # If the OS is MacOS we can ask the Application to draw using the MacOSFactory, 142 | # otherwise, if the OS is Windows we can pass the WindowsFactory instead. 143 | # 144 | # RU:Это пример использования в реальном приложении. 145 | # Если ОС MacOS, мы можем попросить приложение рисовать с помощью MacOSFactory, 146 | # в противном случае, если ОС — Windows, мы можем вместо этого передать 147 | # WindowsFactory. 148 | # 149 | current_os = 'Windows' 150 | factory = nil 151 | 152 | case current_os 153 | when 'MacOS' 154 | factory = MacOSFactory.new 155 | when 'Windows' 156 | factory = WindowsFactory.new 157 | end 158 | Application.new(factory).draw 159 | -------------------------------------------------------------------------------- /src/proxy/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Proxy Design Pattern 2 | # 3 | # Intent: Provide a surrogate or placeholder for another object to control access 4 | # to the original object or to add other responsibilities. 5 | # 6 | # RU: Паттерн Заместитель 7 | # 8 | # Назначение: Позволяет подставлять вместо реальных объектов специальные 9 | # объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту, 10 | # позволяя сделать что-то до или после передачи вызова оригиналу. 11 | 12 | # EN: The Subject interface declares common operations for both RealSubject 13 | # and the Proxy. As long as the client works with RealSubject using this 14 | # interface, you'll be able to pass it a proxy instead of a real subject. 15 | # 16 | # RU: Интерфейс Субъекта объявляет общие операции как для Реального Субъекта, 17 | # так и для Заместителя. Пока клиент работает с Реальным Субъектом, используя 18 | # этот интерфейс, вы сможете передать ему заместителя вместо реального 19 | # субъекта. 20 | # 21 | # @abstract 22 | class Subject 23 | # @abstract 24 | def request 25 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 26 | end 27 | end 28 | 29 | # EN: The RealSubject contains some core business logic. Usually, RealSubjects 30 | # are capable of doing some useful work which may also be very slow or 31 | # sensitive - e.g. correcting input data. A Proxy can solve these issues 32 | # without any changes to the RealSubject's code. 33 | # 34 | # RU: Реальный Субъект содержит некоторую базовую бизнес-логику. Как правило, 35 | # Реальные Субъекты способны выполнять некоторую полезную работу, которая к 36 | # тому же может быть очень медленной или точной – например, коррекция входных 37 | # данных. Заместитель может решить эти задачи без каких-либо изменений в коде 38 | # Реального Субъекта. 39 | class RealSubject < Subject 40 | def request 41 | puts 'RealSubject: Handling request.' 42 | end 43 | end 44 | 45 | # EN: The Proxy has an interface identical to the RealSubject. 46 | # 47 | # RU: Интерфейс Заместителя идентичен интерфейсу Реального Субъекта. 48 | class Proxy < Subject 49 | # @param [RealSubject] real_subject 50 | def initialize(real_subject) 51 | @real_subject = real_subject 52 | end 53 | 54 | # EN: The most common applications of the Proxy pattern are lazy loading, 55 | # caching, controlling the access, logging, etc. A Proxy can perform one 56 | # of these things and then, depending on the result, pass the execution to 57 | # the same method in a linked RealSubject object. 58 | # 59 | # RU: Наиболее распространёнными областями применения паттерна Заместитель 60 | # являются ленивая загрузка, кэширование, контроль доступа, ведение 61 | # журнала и т.д. Заместитель может выполнить одну из этих задач, а затем, 62 | # в зависимости от результата, передать выполнение одноимённому методу в 63 | # связанном объекте класса Реального Субъекта. 64 | def request 65 | return unless check_access 66 | 67 | @real_subject.request 68 | log_access 69 | end 70 | 71 | # @return [Boolean] 72 | def check_access 73 | puts 'Proxy: Checking access prior to firing a real request.' 74 | true 75 | end 76 | 77 | def log_access 78 | print 'Proxy: Logging the time of request.' 79 | end 80 | end 81 | 82 | # EN: The client code is supposed to work with all objects (both subjects and 83 | # proxies) via the Subject interface in order to support both real subjects 84 | # and proxies. In real life, however, clients mostly work with their real 85 | # subjects directly. In this case, to implement the pattern more easily, you 86 | # can extend your proxy from the real subject's class. 87 | # 88 | # RU: Клиентский код должен работать со всеми объектами (как с реальными, так 89 | # и заместителями) через интерфейс Субъекта, чтобы поддерживать как реальные 90 | # субъекты, так и заместителей. В реальной жизни, однако, клиенты в основном 91 | # работают с реальными субъектами напрямую. В этом случае, для более простой 92 | # реализации паттерна, можно расширить заместителя из класса реального 93 | # субъекта. 94 | # 95 | # @param [Subject] subject 96 | def client_code(subject) 97 | # ... 98 | 99 | subject.request 100 | 101 | # ... 102 | end 103 | 104 | puts 'Client: Executing the client code with a real subject:' 105 | real_subject = RealSubject.new 106 | client_code(real_subject) 107 | 108 | puts "\n" 109 | 110 | puts 'Client: Executing the same client code with a proxy:' 111 | proxy = Proxy.new(real_subject) 112 | client_code(proxy) 113 | -------------------------------------------------------------------------------- /src/chain_of_responsibility/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Chain of Responsibility Design Pattern 2 | # 3 | # Intent: Lets you pass requests along a chain of handlers. Upon receiving a 4 | # request, each handler decides either to process the request or to pass it to the 5 | # next handler in the chain. 6 | # 7 | # RU: Паттерн Цепочка обязанностей 8 | # 9 | # Назначение: Позволяет передавать запросы последовательно по цепочке 10 | # обработчиков. Каждый последующий обработчик решает, может ли он обработать 11 | # запрос сам и стоит ли передавать запрос дальше по цепи. 12 | 13 | # EN: The Handler interface declares a method for building the chain of 14 | # handlers. It also declares a method for executing a request. 15 | # 16 | # RU: Интерфейс Обработчика объявляет метод построения цепочки обработчиков. 17 | # Он также объявляет метод для выполнения запроса. 18 | # 19 | # @abstract 20 | class Handler 21 | # @abstract 22 | # 23 | # @param [Handler] handler 24 | def next_handler=(handler) 25 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 26 | end 27 | 28 | # @abstract 29 | # 30 | # @param [String] request 31 | # 32 | # @return [String, nil] 33 | def handle(request) 34 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 35 | end 36 | end 37 | 38 | # EN: The default chaining behavior can be implemented inside a base handler 39 | # class. 40 | # 41 | # RU: Поведение цепочки по умолчанию может быть реализовано внутри базового 42 | # класса обработчика. 43 | class AbstractHandler < Handler 44 | # @return [Handler] 45 | attr_writer :next_handler 46 | 47 | # @param [Handler] handler 48 | # 49 | # @return [Handler] 50 | def next_handler(handler) 51 | @next_handler = handler 52 | # EN: Returning a handler from here will let us link handlers in a 53 | # convenient way like this: 54 | # monkey.next_handler(squirrel).next_handler(dog) 55 | # 56 | # RU: Возврат обработчика отсюда позволит связать обработчики простым 57 | # способом, вот так: 58 | # monkey.next_handler(squirrel).next_handler(dog) 59 | handler 60 | end 61 | 62 | # @abstract 63 | # 64 | # @param [String] request 65 | # 66 | # @return [String, nil] 67 | def handle(request) 68 | return @next_handler.handle(request) if @next_handler 69 | 70 | nil 71 | end 72 | end 73 | 74 | # EN: All Concrete Handlers either handle a request or pass it to the next handler 75 | # in the chain. 76 | # 77 | # RU: Все Конкретные Обработчики либо обрабатывают запрос, либо передают его 78 | # следующему обработчику в цепочке. 79 | class MonkeyHandler < AbstractHandler 80 | # @param [String] request 81 | # 82 | # @return [String, nil] 83 | def handle(request) 84 | if request == 'Banana' 85 | "Monkey: I'll eat the #{request}" 86 | else 87 | super(request) 88 | end 89 | end 90 | end 91 | 92 | class SquirrelHandler < AbstractHandler 93 | # @param [String] request 94 | # 95 | # @return [String, nil] 96 | def handle(request) 97 | if request == 'Nut' 98 | "Squirrel: I'll eat the #{request}" 99 | else 100 | super(request) 101 | end 102 | end 103 | end 104 | 105 | class DogHandler < AbstractHandler 106 | # @param [String] request 107 | # 108 | # @return [String, nil] 109 | def handle(request) 110 | if request == 'MeatBall' 111 | "Dog: I'll eat the #{request}" 112 | else 113 | super(request) 114 | end 115 | end 116 | end 117 | 118 | # EN: The client code is usually suited to work with a single handler. In most 119 | # cases, it is not even aware that the handler is part of a chain. 120 | # 121 | # RU: Обычно клиентский код приспособлен для работы с единственным 122 | # обработчиком. В большинстве случаев клиенту даже неизвестно, что этот 123 | # обработчик является частью цепочки. 124 | # 125 | # @param [Handler] handler 126 | def client_code(handler) 127 | ['Nut', 'Banana', 'Cup of coffee'].each do |food| 128 | puts "\nClient: Who wants a #{food}?" 129 | result = handler.handle(food) 130 | if result 131 | print " #{result}" 132 | else 133 | print " #{food} was left untouched." 134 | end 135 | end 136 | end 137 | 138 | monkey = MonkeyHandler.new 139 | squirrel = SquirrelHandler.new 140 | dog = DogHandler.new 141 | 142 | monkey.next_handler(squirrel).next_handler(dog) 143 | 144 | # EN: The client should be able to send a request to any handler, not just 145 | # the first one in the chain. 146 | # 147 | # RU: Клиент должен иметь возможность отправлять запрос любому обработчику, 148 | # а не только первому в цепочке. 149 | puts 'Chain: Monkey > Squirrel > Dog' 150 | client_code(monkey) 151 | puts "\n\n" 152 | 153 | puts 'Subchain: Squirrel > Dog' 154 | client_code(squirrel) 155 | -------------------------------------------------------------------------------- /src/command/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Command Design Pattern 2 | # 3 | # Intent: Turns a request into a stand-alone object that contains all information 4 | # about the request. This transformation lets you parameterize methods with 5 | # different requests, delay or queue a request's execution, and support undoable 6 | # operations. 7 | # 8 | # RU: Паттерн Команда 9 | # 10 | # Назначение: Превращает запросы в объекты, позволяя передавать их как аргументы 11 | # при вызове методов, ставить запросы в очередь, логировать их, а также 12 | # поддерживать отмену операций. 13 | 14 | # EN: The Command interface declares a method for executing a command. 15 | # 16 | # RU: Интерфейс Команды объявляет метод для выполнения команд. 17 | # 18 | # @abstract 19 | class Command 20 | # @abstract 21 | def execute 22 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 23 | end 24 | end 25 | 26 | # EN: Some commands can implement simple operations on their own. 27 | # 28 | # RU: Некоторые команды способны выполнять простые операции самостоятельно. 29 | class SimpleCommand < Command 30 | # @param [String] payload 31 | def initialize(payload) 32 | @payload = payload 33 | end 34 | 35 | def execute 36 | puts "SimpleCommand: See, I can do simple things like printing (#{@payload})" 37 | end 38 | end 39 | 40 | # EN: However, some commands can delegate more complex operations to other 41 | # objects, called "receivers". 42 | # 43 | # RU: Но есть и команды, которые делегируют более сложные операции другим 44 | # объектам, называемым «получателями». 45 | class ComplexCommand < Command 46 | # EN: Complex commands can accept one or several receiver objects along 47 | # with any context data via the constructor. 48 | # 49 | # RU: Сложные команды могут принимать один или несколько 50 | # объектов-получателей вместе с любыми данными о контексте через 51 | # конструктор. 52 | # 53 | # @param [Receiver] receiver 54 | # @param [String] a 55 | # @param [String] b 56 | def initialize(receiver, a, b) 57 | @receiver = receiver 58 | @a = a 59 | @b = b 60 | end 61 | 62 | # EN: Commands can delegate to any methods of a receiver. 63 | # 64 | # RU: Команды могут делегировать выполнение любым методам получателя. 65 | def execute 66 | print 'ComplexCommand: Complex stuff should be done by a receiver object' 67 | @receiver.do_something(@a) 68 | @receiver.do_something_else(@b) 69 | end 70 | end 71 | 72 | # EN: The Receiver classes contain some important business logic. They know 73 | # how to perform all kinds of operations, associated with carrying out a 74 | # request. In fact, any class may serve as a Receiver. 75 | # 76 | # RU: Классы Получателей содержат некую важную бизнес-логику. Они умеют 77 | # выполнять все виды операций, связанных с выполнением запроса. Фактически, 78 | # любой класс может выступать Получателем. 79 | class Receiver 80 | # @param [String] a 81 | def do_something(a) 82 | print "\nReceiver: Working on (#{a}.)" 83 | end 84 | 85 | # @param [String] b 86 | def do_something_else(b) 87 | print "\nReceiver: Also working on (#{b}.)" 88 | end 89 | end 90 | 91 | # EN: The Invoker is associated with one or several commands. It sends a 92 | # request to the command. 93 | # 94 | # RU: Отправитель связан с одной или несколькими командами. Он отправляет 95 | # запрос команде. 96 | class Invoker 97 | # EN: Initialize commands. 98 | # 99 | # RU: Инициализация команд. 100 | 101 | # @param [Command] command 102 | def on_start=(command) 103 | @on_start = command 104 | end 105 | 106 | # @param [Command] command 107 | def on_finish=(command) 108 | @on_finish = command 109 | end 110 | 111 | # EN: The Invoker does not depend on concrete command or receiver classes. 112 | # The Invoker passes a request to a receiver indirectly, by executing a 113 | # command. 114 | # 115 | # RU: Отправитель не зависит от классов конкретных команд и получателей. 116 | # Отправитель передаёт запрос получателю косвенно, выполняя команду. 117 | def do_something_important 118 | puts 'Invoker: Does anybody want something done before I begin?' 119 | @on_start.execute if @on_start.is_a? Command 120 | 121 | puts 'Invoker: ...doing something really important...' 122 | 123 | puts 'Invoker: Does anybody want something done after I finish?' 124 | @on_finish.execute if @on_finish.is_a? Command 125 | end 126 | end 127 | 128 | # EN: The client code can parameterize an invoker with any commands. 129 | # 130 | # RU: Клиентский код может параметризовать отправителя любыми командами. 131 | invoker = Invoker.new 132 | invoker.on_start = SimpleCommand.new('Say Hi!') 133 | receiver = Receiver.new 134 | invoker.on_finish = ComplexCommand.new(receiver, 'Send email', 'Save report') 135 | 136 | invoker.do_something_important 137 | -------------------------------------------------------------------------------- /src/facade/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Facade Design Pattern 2 | # 3 | # Intent: Provides a simplified interface to a library, a framework, or any other 4 | # complex set of classes. 5 | # 6 | # RU: Паттерн Фасад 7 | # 8 | # Назначение: Предоставляет простой интерфейс к сложной системе классов, 9 | # библиотеке или фреймворку. 10 | 11 | # EN: The Facade class provides a simple interface to the complex logic of one 12 | # or several subsystems. The Facade delegates the client requests to the 13 | # appropriate objects within the subsystem. The Facade is also responsible for 14 | # managing their lifecycle. All of this shields the client from the undesired 15 | # complexity of the subsystem. 16 | # 17 | # RU: Класс Фасада предоставляет простой интерфейс для сложной логики одной 18 | # или нескольких подсистем. Фасад делегирует запросы клиентов соответствующим 19 | # объектам внутри подсистемы. Фасад также отвечает за управление их жизненным 20 | # циклом. Все это защищает клиента от нежелательной сложности подсистемы. 21 | class Facade 22 | # EN: Depending on your application's needs, you can provide the Facade 23 | # with existing subsystem objects or force the Facade to create them on 24 | # its own. 25 | # 26 | # RU: В зависимости от потребностей вашего приложения вы можете 27 | # предоставить Фасаду существующие объекты подсистемы или заставить Фасад 28 | # создать их самостоятельно. 29 | # 30 | # @param [Subsystem1] subsystem1 31 | # @param [Subsystem2] subsystem2 32 | def initialize(subsystem1, subsystem2) 33 | @subsystem1 = subsystem1 || Subsystem1.new 34 | @subsystem2 = subsystem2 || Subsystem2.new 35 | end 36 | 37 | # EN: The Facade's methods are convenient shortcuts to the sophisticated 38 | # functionality of the subsystems. However, clients get only to a fraction 39 | # of a subsystem's capabilities. 40 | # 41 | # RU: Методы Фасада удобны для быстрого доступа к сложной функциональности 42 | # подсистем. Однако клиенты получают только часть возможностей подсистемы. 43 | # 44 | # @return [String] 45 | def operation 46 | results = [] 47 | results.append('Facade initializes subsystems:') 48 | results.append(@subsystem1.operation1) 49 | results.append(@subsystem2.operation1) 50 | results.append('Facade orders subsystems to perform the action:') 51 | results.append(@subsystem1.operation_n) 52 | results.append(@subsystem2.operation_z) 53 | results.join("\n") 54 | end 55 | end 56 | 57 | # EN: The Subsystem can accept requests either from the facade or client 58 | # directly. In any case, to the Subsystem, the Facade is yet another client, 59 | # and it's not a part of the Subsystem. 60 | # 61 | # RU: Подсистема может принимать запросы либо от фасада, либо от клиента 62 | # напрямую. В любом случае, для Подсистемы Фасад – это ещё один клиент, и он 63 | # не является частью Подсистемы. 64 | class Subsystem1 65 | # @return [String] 66 | def operation1 67 | 'Subsystem1: Ready!' 68 | end 69 | 70 | # ... 71 | 72 | # @return [String] 73 | def operation_n 74 | 'Subsystem1: Go!' 75 | end 76 | end 77 | 78 | # EN: Some facades can work with multiple subsystems at the same time. 79 | # 80 | # RU: Некоторые фасады могут работать с разными подсистемами одновременно. 81 | class Subsystem2 82 | # @return [String] 83 | def operation1 84 | 'Subsystem2: Get ready!' 85 | end 86 | 87 | # ... 88 | 89 | # @return [String] 90 | def operation_z 91 | 'Subsystem2: Fire!' 92 | end 93 | end 94 | 95 | # EN: The client code works with complex subsystems through a simple interface 96 | # provided by the Facade. When a facade manages the lifecycle of the 97 | # subsystem, the client might not even know about the existence of the 98 | # subsystem. This approach lets you keep the complexity under control. 99 | # 100 | # RU: Клиентский код работает со сложными подсистемами через простой 101 | # интерфейс, предоставляемый Фасадом. Когда фасад управляет жизненным циклом 102 | # подсистемы, клиент может даже не знать о существовании подсистемы. Такой 103 | # подход позволяет держать сложность под контролем. 104 | # 105 | # @param [Facade] facade 106 | def client_code(facade) 107 | print facade.operation 108 | end 109 | 110 | # EN: The client code may have some of the subsystem's objects already 111 | # created. In this case, it might be worthwhile to initialize the Facade 112 | # with these objects instead of letting the Facade create new instances. 113 | # 114 | # RU: В клиентском коде могут быть уже созданы некоторые объекты подсистемы. 115 | # В этом случае может оказаться целесообразным инициализировать Фасад с 116 | # этими объектами вместо того, чтобы позволить Фасаду создавать новые 117 | # экземпляры. 118 | subsystem1 = Subsystem1.new 119 | subsystem2 = Subsystem2.new 120 | facade = Facade.new(subsystem1, subsystem2) 121 | client_code(facade) 122 | -------------------------------------------------------------------------------- /src/decorator/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Decorator Design Pattern 2 | # 3 | # Intent: Lets you attach new behaviors to objects by placing these objects inside 4 | # special wrapper objects that contain the behaviors. 5 | # 6 | # RU: Паттерн Декоратор 7 | # 8 | # Назначение: Позволяет динамически добавлять объектам новую функциональность, 9 | # оборачивая их в полезные «обёртки». 10 | 11 | # EN: The base Component interface defines operations that can be altered by 12 | # decorators. 13 | # 14 | # RU: Базовый интерфейс Компонента определяет поведение, которое изменяется 15 | # декораторами. 16 | class Component 17 | # @return [String] 18 | def operation 19 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 20 | end 21 | end 22 | 23 | # EN: Concrete Components provide default implementations of the operations. 24 | # There might be several variations of these classes. 25 | # 26 | # RU: Конкретные Компоненты предоставляют реализации поведения по умолчанию. 27 | # Может быть несколько вариаций этих классов. 28 | class ConcreteComponent < Component 29 | # @return [String] 30 | def operation 31 | 'ConcreteComponent' 32 | end 33 | end 34 | 35 | # EN: The base Decorator class follows the same interface as the other 36 | # components. The primary purpose of this class is to define the wrapping 37 | # interface for all concrete decorators. The default implementation of the 38 | # wrapping code might include a field for storing a wrapped component and the 39 | # means to initialize it. 40 | # 41 | # RU: Базовый класс Декоратора следует тому же интерфейсу, что и другие 42 | # компоненты. Основная цель этого класса - определить интерфейс обёртки для 43 | # всех конкретных декораторов. Реализация кода обёртки по умолчанию может 44 | # включать в себя поле для хранения завёрнутого компонента и средства его 45 | # инициализации. 46 | class Decorator < Component 47 | attr_accessor :component 48 | 49 | # @param [Component] component 50 | def initialize(component) 51 | @component = component 52 | end 53 | 54 | # EN: The Decorator delegates all work to the wrapped component. 55 | # 56 | # RU: Декоратор делегирует всю работу обёрнутому компоненту. 57 | # 58 | # @return [String] 59 | def operation 60 | @component.operation 61 | end 62 | end 63 | 64 | # EN: Concrete Decorators call the wrapped object and alter its result in some 65 | # way. 66 | # 67 | # RU: Конкретные Декораторы вызывают обёрнутый объект и изменяют его результат 68 | # некоторым образом. 69 | class ConcreteDecoratorA < Decorator 70 | # EN: Decorators may call parent implementation of the operation, instead 71 | # of calling the wrapped object directly. This approach simplifies 72 | # extension of decorator classes. 73 | # 74 | # RU: Декораторы могут вызывать родительскую реализацию операции, вместо 75 | # того, чтобы вызвать обёрнутый объект напрямую. Такой подход упрощает 76 | # расширение классов декораторов. 77 | # 78 | # @return [String] 79 | def operation 80 | "ConcreteDecoratorA(#{@component.operation})" 81 | end 82 | end 83 | 84 | # EN: Decorators can execute their behavior either before or after the call to 85 | # a wrapped object. 86 | # 87 | # RU: Декораторы могут выполнять своё поведение до или после вызова обёрнутого 88 | # объекта. 89 | class ConcreteDecoratorB < Decorator 90 | # @return [String] 91 | def operation 92 | "ConcreteDecoratorB(#{@component.operation})" 93 | end 94 | end 95 | 96 | # EN: The client code works with all objects using the Component interface. 97 | # This way it can stay independent of the concrete classes of components it 98 | # works with. 99 | # 100 | # RU: Клиентский код работает со всеми объектами, используя интерфейс 101 | # Компонента. Таким образом, он остаётся независимым от конкретных классов 102 | # компонентов, с которыми работает. 103 | # 104 | # @param [Component] component 105 | def client_code(component) 106 | # ... 107 | 108 | print "RESULT: #{component.operation}" 109 | 110 | # ... 111 | end 112 | 113 | # EN: This way the client code can support both simple components... 114 | # 115 | # RU: Таким образом, клиентский код может поддерживать как простые 116 | # компоненты... 117 | simple = ConcreteComponent.new 118 | puts 'Client: I\'ve got a simple component:' 119 | client_code(simple) 120 | puts "\n\n" 121 | 122 | # EN: ...as well as decorated ones. 123 | # 124 | # Note how decorators can wrap not only simple components but the other 125 | # decorators as well. 126 | # 127 | # RU: ...так и декорированные. 128 | # 129 | # Обратите внимание, что декораторы могут обёртывать не только простые 130 | # компоненты, но и другие декораторы. 131 | decorator1 = ConcreteDecoratorA.new(simple) 132 | decorator2 = ConcreteDecoratorB.new(decorator1) 133 | puts 'Client: Now I\'ve got a decorated component:' 134 | client_code(decorator2) 135 | -------------------------------------------------------------------------------- /src/flyweight/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Flyweight Design Pattern 2 | # 3 | # Intent: Lets you fit more objects into the available amount of RAM by sharing 4 | # common parts of state between multiple objects, instead of keeping all of the 5 | # data in each object. 6 | # 7 | # RU: Паттерн Легковес 8 | # 9 | # Назначение: Позволяет вместить бóльшее количество объектов в отведённую 10 | # оперативную память. Легковес экономит память, разделяя общее состояние объектов 11 | # между собой, вместо хранения одинаковых данных в каждом объекте. 12 | 13 | require 'json' 14 | 15 | # EN: The Flyweight stores a common portion of the state (also called 16 | # intrinsic state) that belongs to multiple real business entities. The 17 | # Flyweight accepts the rest of the state (extrinsic state, unique for each 18 | # entity) via its method parameters. 19 | # 20 | # RU: Легковес хранит общую часть состояния (также называемую внутренним 21 | # состоянием), которая принадлежит нескольким реальным бизнес-объектам. 22 | # Легковес принимает оставшуюся часть состояния (внешнее состояние, уникальное 23 | # для каждого объекта) через его параметры метода. 24 | class Flyweight 25 | # @param [String] shared_state 26 | def initialize(shared_state) 27 | @shared_state = shared_state 28 | end 29 | 30 | # @param [String] unique_state 31 | def operation(unique_state) 32 | s = @shared_state.to_json 33 | u = unique_state.to_json 34 | print "Flyweight: Displaying shared (#{s}) and unique (#{u}) state." 35 | end 36 | end 37 | 38 | # EN: The Flyweight Factory creates and manages the Flyweight objects. It 39 | # ensures that flyweights are shared correctly. When the client requests a 40 | # flyweight, the factory either returns an existing instance or creates a new 41 | # one, if it doesn't exist yet. 42 | # 43 | # RU: Фабрика Легковесов создает объекты-Легковесы и управляет ими. Она 44 | # обеспечивает правильное разделение легковесов. Когда клиент запрашивает 45 | # легковес, фабрика либо возвращает существующий экземпляр, либо создает 46 | # новый, если он ещё не существует. 47 | class FlyweightFactory 48 | # @param [Hash] initial_flyweights 49 | def initialize(initial_flyweights) 50 | @flyweights = {} 51 | initial_flyweights.each do |state| 52 | @flyweights[get_key(state)] = Flyweight.new(state) 53 | end 54 | end 55 | 56 | # EN: Returns a Flyweight's string hash for a given state. 57 | # 58 | # RU: Возвращает хеш строки Легковеса для данного состояния. 59 | # 60 | # @param [Array] state 61 | # 62 | # @return [String] 63 | def get_key(state) 64 | state.sort.join('_') 65 | end 66 | 67 | # EN: Returns an existing Flyweight with a given state or creates a new 68 | # one. 69 | # 70 | # RU: Возвращает существующий Легковес с заданным состоянием или создает 71 | # новый. 72 | # 73 | # @param [Array] shared_state 74 | # 75 | # @return [Flyweight] 76 | def get_flyweight(shared_state) 77 | key = get_key(shared_state) 78 | 79 | if !@flyweights.key?(key) 80 | puts "FlyweightFactory: Can't find a flyweight, creating new one." 81 | @flyweights[key] = Flyweight.new(shared_state) 82 | else 83 | puts 'FlyweightFactory: Reusing existing flyweight.' 84 | end 85 | 86 | @flyweights[key] 87 | end 88 | 89 | def list_flyweights 90 | puts "FlyweightFactory: I have #{@flyweights.size} flyweights:" 91 | print @flyweights.keys.join("\n") 92 | end 93 | end 94 | 95 | # @param [FlyweightFactory] factory 96 | # @param [String] plates 97 | # @param [String] owner 98 | # @param [String] brand 99 | # @param [String] model 100 | # @param [String] color 101 | def add_car_to_police_database(factory, plates, owner, brand, model, color) 102 | puts "\n\nClient: Adding a car to database." 103 | flyweight = factory.get_flyweight([brand, model, color]) 104 | # EN: The client code either stores or calculates extrinsic state and passes 105 | # it to the flyweight's methods. 106 | # 107 | # RU: Клиентский код либо сохраняет, либо вычисляет внешнее состояние и 108 | # передает его методам легковеса. 109 | flyweight.operation([plates, owner]) 110 | end 111 | 112 | # EN: The client code usually creates a bunch of pre-populated flyweights in 113 | # the initialization stage of the application. 114 | # 115 | # RU: Клиентский код обычно создает кучу предварительно заполненных легковесов 116 | # на этапе инициализации приложения. 117 | 118 | factory = FlyweightFactory.new([ 119 | %w[Chevrolet Camaro2018 pink], 120 | ['Mercedes Benz', 'C300', 'black'], 121 | ['Mercedes Benz', 'C500', 'red'], 122 | %w[BMW M5 red], 123 | %w[BMW X6 white] 124 | ]) 125 | 126 | factory.list_flyweights 127 | 128 | add_car_to_police_database(factory, 'CL234IR', 'James Doe', 'BMW', 'M5', 'red') 129 | 130 | add_car_to_police_database(factory, 'CL234IR', 'James Doe', 'BMW', 'X1', 'red') 131 | 132 | puts "\n\n" 133 | 134 | factory.list_flyweights 135 | -------------------------------------------------------------------------------- /src/template_method/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Template Method Design Pattern 2 | # 3 | # Intent: Defines the skeleton of an algorithm in the superclass but lets 4 | # subclasses override specific steps of the algorithm without changing its 5 | # structure. 6 | # 7 | # RU: Паттерн Шаблонный метод 8 | # 9 | # Назначение: Определяет общую схему алгоритма, перекладывая реализацию некоторых 10 | # шагов на подклассы. Шаблонный метод позволяет подклассам переопределять 11 | # отдельные шаги алгоритма без изменения структуры алгоритма. 12 | 13 | # EN: The Abstract Class defines a template method that contains a skeleton of 14 | # some algorithm, composed of calls to (usually) abstract primitive 15 | # operations. 16 | # 17 | # Concrete subclasses should implement these operations, but leave the 18 | # template method itself intact. 19 | # 20 | # RU: Абстрактный Класс определяет шаблонный метод, содержащий скелет 21 | # некоторого алгоритма, состоящего из вызовов (обычно) абстрактных примитивных 22 | # операций. 23 | # 24 | # Конкретные подклассы должны реализовать эти операции, но оставить сам 25 | # шаблонный метод без изменений. 26 | # 27 | # @abstract 28 | class AbstractClass 29 | # EN: The template method defines the skeleton of an algorithm. 30 | # 31 | # RU: Шаблонный метод определяет скелет алгоритма. 32 | def template_method 33 | base_operation1 34 | required_operations1 35 | base_operation2 36 | hook1 37 | required_operations2 38 | base_operation3 39 | hook2 40 | end 41 | 42 | # EN: These operations already have implementations. 43 | # 44 | # RU: Эти операции уже имеют реализации. 45 | 46 | def base_operation1 47 | puts 'AbstractClass says: I am doing the bulk of the work' 48 | end 49 | 50 | def base_operation2 51 | puts 'AbstractClass says: But I let subclasses override some operations' 52 | end 53 | 54 | def base_operation3 55 | puts 'AbstractClass says: But I am doing the bulk of the work anyway' 56 | end 57 | 58 | # EN: These operations have to be implemented in subclasses. 59 | # 60 | # RU: А эти операции должны быть реализованы в подклассах. 61 | # 62 | # @abstract 63 | def required_operations1 64 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 65 | end 66 | 67 | # @abstract 68 | def required_operations2 69 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 70 | end 71 | 72 | # EN: These are "hooks." Subclasses may override them, but it's not 73 | # mandatory since the hooks already have default (but empty) implementation. 74 | # Hooks provide additional extension points in some crucial places of the 75 | # algorithm. 76 | # 77 | # RU: Это «хуки». Подклассы могут переопределять их, но это не обязательно, 78 | # поскольку у хуков уже есть стандартная (но пустая) реализация. Хуки 79 | # предоставляют дополнительные точки расширения в некоторых критических 80 | # местах алгоритма. 81 | 82 | def hook1; end 83 | 84 | def hook2; end 85 | end 86 | 87 | # EN: Concrete classes have to implement all abstract operations of the base 88 | # class. They can also override some operations with a default implementation. 89 | # 90 | # RU: Конкретные классы должны реализовать все абстрактные операции базового 91 | # класса. Они также могут переопределить некоторые операции с реализацией по 92 | # умолчанию. 93 | class ConcreteClass1 < AbstractClass 94 | def required_operations1 95 | puts 'ConcreteClass1 says: Implemented Operation1' 96 | end 97 | 98 | def required_operations2 99 | puts 'ConcreteClass1 says: Implemented Operation2' 100 | end 101 | end 102 | 103 | # EN: Usually, concrete classes override only a fraction of base class' 104 | # operations. 105 | # 106 | # RU: Обычно конкретные классы переопределяют только часть операций базового 107 | # класса. 108 | class ConcreteClass2 < AbstractClass 109 | def required_operations1 110 | puts 'ConcreteClass2 says: Implemented Operation1' 111 | end 112 | 113 | def required_operations2 114 | puts 'ConcreteClass2 says: Implemented Operation2' 115 | end 116 | 117 | def hook1 118 | puts 'ConcreteClass2 says: Overridden Hook1' 119 | end 120 | end 121 | 122 | # EN: The client code calls the template method to execute the algorithm. 123 | # Client code does not have to know the concrete class of an object it works 124 | # with, as long as it works with objects through the interface of their base 125 | # class. 126 | # 127 | # RU: Клиентский код вызывает шаблонный метод для выполнения алгоритма. 128 | # Клиентский код не должен знать конкретный класс объекта, с которым работает, 129 | # при условии, что он работает с объектами через интерфейс их базового класса. 130 | # 131 | # @param [AbstractClass] abstract_class 132 | def client_code(abstract_class) 133 | # ... 134 | abstract_class.template_method 135 | # ... 136 | end 137 | 138 | puts 'Same client code can work with different subclasses:' 139 | client_code(ConcreteClass1.new) 140 | puts "\n" 141 | 142 | puts 'Same client code can work with different subclasses:' 143 | client_code(ConcreteClass2.new) 144 | -------------------------------------------------------------------------------- /src/bridge/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Bridge Design Pattern 2 | # 3 | # Intent: Lets you split a large class or a set of closely related classes into 4 | # two separate hierarchies—abstraction and implementation—which can be developed 5 | # independently of each other. 6 | # 7 | # A 8 | # / \ A N 9 | # Aa Ab ===> / \ / \ 10 | # / \ / \ Aa(N) Ab(N) 1 2 11 | # Aa1 Aa2 Ab1 Ab2 12 | # 13 | # RU: Паттерн Мост 14 | # 15 | # Назначение: Разделяет один или несколько классов на две отдельные иерархии — 16 | # абстракцию и реализацию, позволяя изменять их независимо друг от друга. 17 | # 18 | # A 19 | # / \ A N 20 | # Aa Ab ===> / \ / \ 21 | # / \ / \ Aa(N) Ab(N) 1 2 22 | # Aa1 Aa2 Ab1 Ab2 23 | 24 | # EN: The Abstraction defines the interface for the "control" part of the two 25 | # class hierarchies. It maintains a reference to an object of the 26 | # Implementation hierarchy and delegates all of the real work to this object. 27 | # 28 | # RU: Абстракция устанавливает интерфейс для «управляющей» части двух иерархий 29 | # классов. Она содержит ссылку на объект из иерархии Реализации и делегирует 30 | # ему всю настоящую работу. 31 | class Abstraction 32 | # @param [Implementation] implementation 33 | def initialize(implementation) 34 | @implementation = implementation 35 | end 36 | 37 | # @return [String] 38 | def operation 39 | "Abstraction: Base operation with:\n"\ 40 | "#{@implementation.operation_implementation}" 41 | end 42 | end 43 | 44 | # EN: You can extend the Abstraction without changing the Implementation 45 | # classes. 46 | # 47 | # RU: Можно расширить Абстракцию без изменения классов Реализации. 48 | class ExtendedAbstraction < Abstraction 49 | # @return [String] 50 | def operation 51 | "ExtendedAbstraction: Extended operation with:\n"\ 52 | "#{@implementation.operation_implementation}" 53 | end 54 | end 55 | 56 | # EN: The Implementation defines the interface for all implementation classes. 57 | # It doesn't have to match the Abstraction's interface. In fact, the two 58 | # interfaces can be entirely different. Typically the Implementation interface 59 | # provides only primitive operations, while the Abstraction defines higher- 60 | # level operations based on those primitives. 61 | # 62 | # RU: Реализация устанавливает интерфейс для всех классов реализации. Он не 63 | # должен соответствовать интерфейсу Абстракции. На практике оба интерфейса 64 | # могут быть совершенно разными. Как правило, интерфейс Реализации 65 | # предоставляет только примитивные операции, в то время как Абстракция 66 | # определяет операции более высокого уровня, основанные на этих примитивах. 67 | # 68 | # @abstract 69 | class Implementation 70 | # @abstract 71 | # 72 | # @return [String] 73 | def operation_implementation 74 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 75 | end 76 | end 77 | 78 | # EN: Each Concrete Implementation corresponds to a specific platform and 79 | # implements the Implementation interface using that platform's API. 80 | # 81 | # RU: Каждая Конкретная Реализация соответствует определённой платформе и 82 | # реализует интерфейс Реализации с использованием API этой платформы. 83 | class ConcreteImplementationA < Implementation 84 | # @return [String] 85 | def operation_implementation 86 | 'ConcreteImplementationA: Here\'s the result on the platform A.' 87 | end 88 | end 89 | 90 | class ConcreteImplementationB < Implementation 91 | # @return [String] 92 | def operation_implementation 93 | 'ConcreteImplementationB: Here\'s the result on the platform B.' 94 | end 95 | end 96 | 97 | # EN: Except for the initialization phase, where an Abstraction object gets 98 | # linked with a specific Implementation object, the client code should only 99 | # depend on the Abstraction class. This way the client code can support any 100 | # abstraction-implementation combination. 101 | # 102 | # RU: За исключением этапа инициализации, когда объект Абстракции связывается 103 | # с определённым объектом Реализации, клиентский код должен зависеть только от 104 | # класса Абстракции. Таким образом, клиентский код может поддерживать любую 105 | # комбинацию абстракции и реализации. 106 | # 107 | # @param [Abstraction] abstraction 108 | def client_code(abstraction) 109 | # ... 110 | 111 | print abstraction.operation 112 | 113 | # ... 114 | end 115 | 116 | # EN: The client code should be able to work with any pre-configured 117 | # abstraction-implementation combination. 118 | # 119 | # RU: Клиентский код должен работать с любой предварительно сконфигурированной 120 | # комбинацией абстракции и реализации. 121 | 122 | implementation = ConcreteImplementationA.new 123 | abstraction = Abstraction.new(implementation) 124 | client_code(abstraction) 125 | 126 | puts "\n\n" 127 | 128 | implementation = ConcreteImplementationB.new 129 | abstraction = ExtendedAbstraction.new(implementation) 130 | client_code(abstraction) 131 | -------------------------------------------------------------------------------- /src/factory_method/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Factory Method Design Pattern 2 | # 3 | # Intent: Provides an interface for creating objects in a superclass, but allows 4 | # subclasses to alter the type of objects that will be created. 5 | # 6 | # RU: Паттерн Фабричный Метод 7 | # 8 | # Назначение: Определяет общий интерфейс для создания объектов в суперклассе, 9 | # позволяя подклассам изменять тип создаваемых объектов. 10 | 11 | # EN: The Creator class declares the factory method that is supposed to return 12 | # an object of a Product class. The Creator's subclasses usually provide the 13 | # implementation of this method. 14 | # 15 | # RU: Класс Создатель объявляет фабричный метод, который должен возвращать 16 | # объект класса Продукт. Подклассы Создателя обычно предоставляют реализацию 17 | # этого метода. 18 | # 19 | # @abstract 20 | class Creator 21 | # EN: Note that the Creator may also provide some default implementation 22 | # of the factory method. 23 | # 24 | # RU: Обратите внимание, что Создатель может также обеспечить реализацию 25 | # фабричного метода по умолчанию. 26 | # 27 | # @abstract 28 | def factory_method 29 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 30 | end 31 | 32 | # EN: Also note that, despite its name, the Creator's primary 33 | # responsibility is not creating products. Usually, it contains some core 34 | # business logic that relies on Product objects, returned by the factory 35 | # method. Subclasses can indirectly change that business logic by 36 | # overriding the factory method and returning a different type of product 37 | # from it. 38 | # 39 | # RU: Также заметьте, что, несмотря на название, основная обязанность 40 | # Создателя не заключается в создании продуктов. Обычно он содержит 41 | # некоторую базовую бизнес-логику, которая основана на объектах Продуктов, 42 | # возвращаемых фабричным методом. Подклассы могут косвенно изменять эту 43 | # бизнес-логику, переопределяя фабричный метод и возвращая из него другой 44 | # тип продукта. 45 | # 46 | # @return [String] 47 | def some_operation 48 | # EN: Call the factory method to create a Product object. 49 | # 50 | # RU: Вызываем фабричный метод, чтобы получить объект-продукт. 51 | product = factory_method 52 | 53 | # EN: Now, use the product. 54 | # 55 | # RU: Далее, работаем с этим продуктом. 56 | "Creator: The same creator's code has just worked with #{product.operation}" 57 | end 58 | end 59 | 60 | # EN: Concrete Creators override the factory method in order to change the 61 | # resulting product's type. 62 | # 63 | # RU: Конкретные Создатели переопределяют фабричный метод для того, чтобы изменить 64 | # тип результирующего продукта. 65 | class ConcreteCreator1 < Creator 66 | # EN: Note that the signature of the method still uses the abstract product 67 | # type, even though the concrete product is actually returned from the method. 68 | # This way the Creator can stay independent of concrete product classes. 69 | # 70 | # RU: Обратите внимание, что сигнатура метода по-прежнему использует тип 71 | # абстрактного продукта, хотя фактически из метода возвращается конкретный 72 | # продукт. Таким образом, Создатель может оставаться независимым от конкретных 73 | # классов продуктов. 74 | # 75 | # @return [ConcreteProduct1] 76 | def factory_method 77 | ConcreteProduct1.new 78 | end 79 | end 80 | 81 | class ConcreteCreator2 < Creator 82 | # @return [ConcreteProduct2] 83 | def factory_method 84 | ConcreteProduct2.new 85 | end 86 | end 87 | 88 | # EN: The Product interface declares the operations that all concrete products 89 | # must implement. 90 | # 91 | # RU: Интерфейс Продукта объявляет операции, которые должны выполнять все 92 | # конкретные продукты. 93 | # 94 | # @abstract 95 | class Product 96 | # return [String] 97 | def operation 98 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 99 | end 100 | end 101 | 102 | # EN: Concrete Products provide various implementations of the Product interface. 103 | # 104 | # RU: Конкретные Продукты предоставляют различные реализации интерфейса Продукта. 105 | class ConcreteProduct1 < Product 106 | # @return [String] 107 | def operation 108 | '{Result of the ConcreteProduct1}' 109 | end 110 | end 111 | 112 | class ConcreteProduct2 < Product 113 | # @return [String] 114 | def operation 115 | '{Result of the ConcreteProduct2}' 116 | end 117 | end 118 | 119 | # EN: The client code works with an instance of a concrete creator, albeit 120 | # through its base interface. As long as the client keeps working with the 121 | # creator via the base interface, you can pass it any creator's subclass. 122 | # 123 | # RU: Клиентский код работает с экземпляром конкретного создателя, хотя и 124 | # через его базовый интерфейс. Пока клиент продолжает работать с создателем 125 | # через базовый интерфейс, вы можете передать ему любой подкласс создателя. 126 | # 127 | # @param [Creator] creator 128 | def client_code(creator) 129 | print "Client: I'm not aware of the creator's class, but it still works.\n"\ 130 | "#{creator.some_operation}" 131 | end 132 | 133 | puts 'App: Launched with the ConcreteCreator1.' 134 | client_code(ConcreteCreator1.new) 135 | puts "\n\n" 136 | 137 | puts 'App: Launched with the ConcreteCreator2.' 138 | client_code(ConcreteCreator2.new) 139 | -------------------------------------------------------------------------------- /src/memento/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Memento Design Pattern 2 | # 3 | # Intent: Lets you save and restore the previous state of an object without 4 | # revealing the details of its implementation. 5 | # 6 | # RU: Паттерн Снимок 7 | # 8 | # Назначение: Фиксирует и восстанавливает внутреннее состояние объекта таким 9 | # образом, чтобы в дальнейшем объект можно было восстановить в этом состоянии без 10 | # нарушения инкапсуляции. 11 | 12 | # EN: The Originator holds some important state that may change over time. It 13 | # also defines a method for saving the state inside a memento and another 14 | # method for restoring the state from it. 15 | # 16 | # RU: Создатель содержит некоторое важное состояние, которое может со временем 17 | # меняться. Он также объявляет метод сохранения состояния внутри снимка и 18 | # метод восстановления состояния из него. 19 | class Originator 20 | # EN: For the sake of simplicity, the originator's state is stored inside a 21 | # single variable. 22 | # 23 | # RU: Для удобства состояние создателя хранится внутри одной переменной. 24 | attr_accessor :state 25 | private :state 26 | 27 | # @param [String] state 28 | def initialize(state) 29 | @state = state 30 | puts "Originator: My initial state is: #{@state}" 31 | end 32 | 33 | # EN: The Originator's business logic may affect its internal state. 34 | # Therefore, the client should backup the state before launching methods 35 | # of the business logic via the save() method. 36 | # 37 | # RU: Бизнес-логика Создателя может повлиять на его внутреннее состояние. 38 | # Поэтому клиент должен выполнить резервное копирование состояния с 39 | # помощью метода save перед запуском методов бизнес-логики. 40 | def do_something 41 | puts 'Originator: I\'m doing something important.' 42 | @state = generate_random_string(30) 43 | puts "Originator: and my state has changed to: #{@state}" 44 | end 45 | 46 | private def generate_random_string(length = 10) 47 | ascii_letters = [*'a'..'z', *'A'..'Z'] 48 | (0...length).map { ascii_letters.sample }.join 49 | end 50 | 51 | # EN: Saves the current state inside a memento. 52 | # 53 | # RU: Сохраняет текущее состояние внутри снимка. 54 | # 55 | # @return [Memento] 56 | def save 57 | ConcreteMemento.new(@state) 58 | end 59 | 60 | # EN: Restores the Originator's state from a memento object. 61 | # 62 | # RU: Восстанавливает состояние Создателя из объекта снимка. 63 | # 64 | # @param [Memento] memento 65 | def restore(memento) 66 | @state = memento.state 67 | puts "Originator: My state has changed to: #{@state}" 68 | end 69 | end 70 | 71 | # EN: The Memento interface provides a way to retrieve the memento's metadata, 72 | # such as creation date or name. However, it doesn't expose the Originator's 73 | # state. 74 | # 75 | # RU: Интерфейс Снимка предоставляет способ извлечения метаданных снимка, 76 | # таких как дата создания или название. Однако он не раскрывает состояние 77 | # Создателя. 78 | # 79 | # @abstract 80 | class Memento 81 | # @abstract 82 | # 83 | # @return [String] 84 | def name 85 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 86 | end 87 | 88 | # @abstract 89 | # 90 | # @return [String] 91 | def date 92 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 93 | end 94 | end 95 | 96 | class ConcreteMemento < Memento 97 | # @param [String] state 98 | def initialize(state) 99 | @state = state 100 | @date = Time.now.strftime('%F %T') 101 | end 102 | 103 | # EN: The Originator uses this method when restoring its state. 104 | # 105 | # RU: Создатель использует этот метод, когда восстанавливает своё 106 | # состояние. 107 | # 108 | # @return [String] 109 | attr_reader :state 110 | 111 | # EN: The rest of the methods are used by the Caretaker to display 112 | # metadata. 113 | # 114 | # RU: Остальные методы используются Опекуном для отображения метаданных. 115 | # 116 | # @return [String] 117 | def name 118 | "#{@date} / (#{@state[0, 9]}...)" 119 | end 120 | 121 | # @return [String] 122 | attr_reader :date 123 | end 124 | 125 | # EN: The Caretaker doesn't depend on the Concrete Memento class. Therefore, 126 | # it doesn't have access to the originator's state, stored inside the memento. 127 | # It works with all mementos via the base Memento interface. 128 | # 129 | # RU: Опекун не зависит от класса Конкретного Снимка. Таким образом, он не 130 | # имеет доступа к состоянию создателя, хранящемуся внутри снимка. Он работает 131 | # со всеми снимками через базовый интерфейс Снимка. 132 | class Caretaker 133 | # @param [Originator] originator 134 | def initialize(originator) 135 | @mementos = [] 136 | @originator = originator 137 | end 138 | 139 | def backup 140 | puts "\nCaretaker: Saving Originator's state..." 141 | @mementos << @originator.save 142 | end 143 | 144 | def undo 145 | return if @mementos.empty? 146 | 147 | memento = @mementos.pop 148 | puts "Caretaker: Restoring state to: #{memento.name}" 149 | 150 | begin 151 | @originator.restore(memento) 152 | rescue StandardError 153 | undo 154 | end 155 | end 156 | 157 | def show_history 158 | puts 'Caretaker: Here\'s the list of mementos:' 159 | 160 | @mementos.each { |memento| puts memento.name } 161 | end 162 | end 163 | 164 | originator = Originator.new('Super-duper-super-puper-super.') 165 | caretaker = Caretaker.new(originator) 166 | 167 | caretaker.backup 168 | originator.do_something 169 | 170 | caretaker.backup 171 | originator.do_something 172 | 173 | caretaker.backup 174 | originator.do_something 175 | 176 | puts "\n" 177 | caretaker.show_history 178 | 179 | puts "\nClient: Now, let's rollback!\n" 180 | caretaker.undo 181 | 182 | puts "\nClient: Once more!\n" 183 | caretaker.undo 184 | -------------------------------------------------------------------------------- /src/observer/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Observer Design Pattern 2 | # 3 | # Intent: Lets you define a subscription mechanism to notify multiple objects 4 | # about any events that happen to the object they're observing. 5 | # 6 | # Note that there's a lot of different terms with similar meaning associated with 7 | # this pattern. Just remember that the Subject is also called the Publisher and 8 | # the Observer is often called the Subscriber and vice versa. Also the verbs 9 | # "observe", "listen" or "track" usually mean the same thing. 10 | # 11 | # RU: Паттерн Наблюдатель 12 | # 13 | # Назначение: Создаёт механизм подписки, позволяющий одним объектам следить и 14 | # реагировать на события, происходящие в других объектах. 15 | # 16 | # Обратите внимание, что существует множество различных терминов с похожими 17 | # значениями, связанных с этим паттерном. Просто помните, что Субъекта также 18 | # называют Издателем, а Наблюдателя часто называют Подписчиком и наоборот. Также 19 | # глаголы «наблюдать», «слушать» или «отслеживать» обычно означают одно и то же. 20 | 21 | # EN: The Subject interface declares a set of methods for managing 22 | # subscribers. 23 | # 24 | # RU: Интерфейс издателя объявляет набор методов для управлениями 25 | # подписчиками. 26 | # 27 | # @abstract 28 | class Subject 29 | # EN: Attach an observer to the subject. 30 | # 31 | # RU: Присоединяет наблюдателя к издателю. 32 | # 33 | # @abstract 34 | # 35 | # @param [Observer] observer 36 | def attach(observer) 37 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 38 | end 39 | 40 | # EN: Detach an observer from the subject. 41 | # 42 | # RU: Отсоединяет наблюдателя от издателя. 43 | # 44 | # @abstract 45 | # 46 | # @param [Observer] observer 47 | def detach(observer) 48 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 49 | end 50 | 51 | # EN: Notify all observers about an event. 52 | # 53 | # RU: Уведомляет всех наблюдателей о событии. 54 | # 55 | # @abstract 56 | def notify 57 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 58 | end 59 | end 60 | 61 | # EN: The Subject owns some important state and notifies observers when the 62 | # state changes. 63 | # 64 | # RU: Издатель владеет некоторым важным состоянием и оповещает наблюдателей о 65 | # его изменениях. 66 | class ConcreteSubject < Subject 67 | # EN: For the sake of simplicity, the Subject's state, essential to all 68 | # subscribers, is stored in this variable. 69 | # 70 | # RU: Для удобства в этой переменной хранится состояние Издателя, необходимое 71 | # всем подписчикам. 72 | attr_accessor :state 73 | 74 | # @!attribute observers 75 | # @return [Array] 76 | # attr_accessor :observers 77 | # private :observers 78 | 79 | def initialize 80 | @observers = [] 81 | end 82 | 83 | # EN: List of subscribers. In real life, the list of subscribers can be stored 84 | # more comprehensively (categorized by event type, etc.). 85 | # 86 | # RU: Список подписчиков. В реальной жизни список подписчиков может храниться 87 | # в более подробном виде (классифицируется по типу события и т.д.) 88 | 89 | # @param [Observer] observer 90 | def attach(observer) 91 | puts 'Subject: Attached an observer.' 92 | @observers << observer 93 | end 94 | 95 | # @param [Observer] observer 96 | def detach(observer) 97 | @observers.delete(observer) 98 | end 99 | 100 | # EN: The subscription management methods. 101 | # 102 | # RU: Методы управления подпиской. 103 | 104 | # EN: Trigger an update in each subscriber. 105 | # 106 | # RU: Запуск обновления в каждом подписчике. 107 | def notify 108 | puts 'Subject: Notifying observers...' 109 | @observers.each { |observer| observer.update(self) } 110 | end 111 | 112 | # EN: Usually, the subscription logic is only a fraction of what a Subject 113 | # can really do. Subjects commonly hold some important business logic, 114 | # that triggers a notification method whenever something important is 115 | # about to happen (or after it). 116 | # 117 | # RU: Обычно логика подписки – только часть того, что делает Издатель. 118 | # Издатели часто содержат некоторую важную бизнес-логику, которая 119 | # запускает метод уведомления всякий раз, когда должно произойти что-то 120 | # важное (или после этого). 121 | def some_business_logic 122 | puts "\nSubject: I'm doing something important." 123 | @state = rand(0..10) 124 | 125 | puts "Subject: My state has just changed to: #{@state}" 126 | notify 127 | end 128 | end 129 | 130 | # EN: The Observer interface declares the update method, used by subjects. 131 | # 132 | # RU: Интерфейс Наблюдателя объявляет метод уведомления, который издатели 133 | # используют для оповещения своих подписчиков. 134 | # 135 | # @abstract 136 | class Observer 137 | # EN: Receive update from subject. 138 | # 139 | # RU: Получить обновление от субъекта. 140 | # 141 | # @abstract 142 | # 143 | # @param [Subject] subject 144 | def update(_subject) 145 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 146 | end 147 | end 148 | 149 | # EN: Concrete Observers react to the updates issued by the Subject they had been 150 | # attached to. 151 | # 152 | # RU: Конкретные Наблюдатели реагируют на обновления, выпущенные Издателем, к 153 | # которому они прикреплены. 154 | 155 | class ConcreteObserverA < Observer 156 | # @param [Subject] subject 157 | def update(subject) 158 | puts 'ConcreteObserverA: Reacted to the event' if subject.state < 3 159 | end 160 | end 161 | 162 | class ConcreteObserverB < Observer 163 | # @param [Subject] subject 164 | def update(subject) 165 | return unless subject.state.zero? || subject.state >= 2 166 | 167 | puts 'ConcreteObserverB: Reacted to the event' 168 | end 169 | end 170 | 171 | # EN: The client code. 172 | # 173 | # RU: Клиентский код. 174 | 175 | subject = ConcreteSubject.new 176 | 177 | observer_a = ConcreteObserverA.new 178 | subject.attach(observer_a) 179 | 180 | observer_b = ConcreteObserverB.new 181 | subject.attach(observer_b) 182 | 183 | subject.some_business_logic 184 | subject.some_business_logic 185 | 186 | subject.detach(observer_a) 187 | 188 | subject.some_business_logic 189 | -------------------------------------------------------------------------------- /src/visitor/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Visitor Design Pattern 2 | # 3 | # Intent: Lets you separate algorithms from the objects on which they operate. 4 | # 5 | # RU: Паттерн Посетитель 6 | # 7 | # Назначение: Позволяет создавать новые операции, не меняя классы объектов, над 8 | # которыми эти операции могут выполняться. 9 | 10 | # EN: The Component interface declares an `accept` method that should take the 11 | # base visitor interface as an argument. 12 | # 13 | # RU: Интерфейс Компонента объявляет метод accept, который в качестве 14 | # аргумента может получать любой объект, реализующий интерфейс посетителя. 15 | class Component 16 | # @abstract 17 | # 18 | # @param [Visitor] visitor 19 | def accept(_visitor) 20 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 21 | end 22 | end 23 | 24 | # EN: Each Concrete Component must implement the `accept` method in such a way 25 | # that it calls the visitor's method corresponding to the component's class. 26 | # 27 | # RU: Каждый Конкретный Компонент должен реализовать метод accept таким 28 | # образом, чтобы он вызывал метод посетителя, соответствующий классу 29 | # компонента. 30 | class ConcreteComponentA < Component 31 | # EN: Note that we're calling `visitConcreteComponentA`, which matches the 32 | # current class name. This way we let the visitor know the class of the 33 | # component it works with. 34 | # 35 | # RU: Обратите внимание, мы вызываем visitConcreteComponentA, что 36 | # соответствует названию текущего класса. Таким образом мы позволяем 37 | # посетителю узнать, с каким классом компонента он работает. 38 | # 39 | # @param [Visitor] visitor 40 | def accept(visitor) 41 | visitor.visit_concrete_component_a(self) 42 | end 43 | 44 | # EN: Concrete Components may have special methods that don't exist in 45 | # their base class or interface. The Visitor is still able to use these 46 | # methods since it's aware of the component's concrete class. 47 | # 48 | # RU: Конкретные Компоненты могут иметь особые методы, не объявленные в их 49 | # базовом классе или интерфейсе. Посетитель всё же может использовать эти 50 | # методы, поскольку он знает о конкретном классе компонента. 51 | def exclusive_method_of_concrete_component_a 52 | 'A' 53 | end 54 | end 55 | 56 | # EN: Same here: visit_concrete_component_b => ConcreteComponentB 57 | # 58 | # RU: То же самое здесь: visit_concrete_component_b => ConcreteComponentB 59 | class ConcreteComponentB < Component 60 | # @param [Visitor] visitor 61 | def accept(visitor) 62 | visitor.visit_concrete_component_b(self) 63 | end 64 | 65 | def special_method_of_concrete_component_b 66 | 'B' 67 | end 68 | end 69 | 70 | # EN: The Visitor Interface declares a set of visiting methods that correspond 71 | # to component classes. The signature of a visiting method allows the visitor 72 | # to identify the exact class of the component that it's dealing with. 73 | # 74 | # RU: Интерфейс Посетителя объявляет набор методов посещения, соответствующих 75 | # классам компонентов. Сигнатура метода посещения позволяет посетителю 76 | # определить конкретный класс компонента, с которым он имеет дело. 77 | # 78 | # @abstract 79 | class Visitor 80 | # @abstract 81 | # 82 | # @param [ConcreteComponentA] element 83 | def visit_concrete_component_a(_element) 84 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 85 | end 86 | 87 | # @abstract 88 | # 89 | # @param [ConcreteComponentB] element 90 | def visit_concrete_component_b(_element) 91 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 92 | end 93 | end 94 | 95 | # EN: Concrete Visitors implement several versions of the same algorithm, which 96 | # can work with all concrete component classes. 97 | # 98 | # You can experience the biggest benefit of the Visitor pattern when using it with 99 | # a complex object structure, such as a Composite tree. In this case, it might be 100 | # helpful to store some intermediate state of the algorithm while executing 101 | # visitor's methods over various objects of the structure. 102 | # 103 | # RU: Конкретные Посетители реализуют несколько версий одного и того же алгоритма, 104 | # которые могут работать со всеми классами конкретных компонентов. 105 | # 106 | # Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со 107 | # сложной структурой объектов, такой как дерево Компоновщика. В этом случае было 108 | # бы полезно хранить некоторое промежуточное состояние алгоритма при выполнении 109 | # методов посетителя над различными объектами структуры. 110 | class ConcreteVisitor1 < Visitor 111 | def visit_concrete_component_a(element) 112 | puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}" 113 | end 114 | 115 | def visit_concrete_component_b(element) 116 | puts "#{element.special_method_of_concrete_component_b} + #{self.class}" 117 | end 118 | end 119 | 120 | class ConcreteVisitor2 < Visitor 121 | def visit_concrete_component_a(element) 122 | puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}" 123 | end 124 | 125 | def visit_concrete_component_b(element) 126 | puts "#{element.special_method_of_concrete_component_b} + #{self.class}" 127 | end 128 | end 129 | 130 | # EN: The client code can run visitor operations over any set of elements 131 | # without figuring out their concrete classes. The accept operation directs a 132 | # call to the appropriate operation in the visitor object. 133 | # 134 | # RU: Клиентский код может выполнять операции посетителя над любым набором 135 | # элементов, не выясняя их конкретных классов. Операция принятия направляет 136 | # вызов к соответствующей операции в объекте посетителя. 137 | # 138 | # @param [Array] components 139 | # @param [Visitor] visitor 140 | def client_code(components, visitor) 141 | # ... 142 | components.each do |component| 143 | component.accept(visitor) 144 | end 145 | # ... 146 | end 147 | 148 | components = [ConcreteComponentA.new, ConcreteComponentB.new] 149 | 150 | puts 'The client code works with all visitors via the base Visitor interface:' 151 | visitor1 = ConcreteVisitor1.new 152 | client_code(components, visitor1) 153 | 154 | puts 'It allows the same client code to work with different types of visitors:' 155 | visitor2 = ConcreteVisitor2.new 156 | client_code(components, visitor2) 157 | -------------------------------------------------------------------------------- /src/composite/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Composite Design Pattern 2 | # 3 | # Intent: Lets you compose objects into tree structures and then work with these 4 | # structures as if they were individual objects. 5 | # 6 | # RU: Паттерн Компоновщик 7 | # 8 | # Назначение: Позволяет сгруппировать объекты в древовидную структуру, а затем 9 | # работать с ними так, как будто это единичный объект. 10 | 11 | # EN: The base Component class declares common operations for both simple and 12 | # complex objects of a composition. 13 | # 14 | # RU: Базовый класс Компонент объявляет общие операции как для простых, так и 15 | # для сложных объектов структуры. 16 | # 17 | # @abstract 18 | class Component 19 | # @return [Component] 20 | def parent 21 | @parent 22 | end 23 | 24 | # EN: Optionally, the base Component can declare an interface for setting 25 | # and accessing a parent of the component in a tree structure. It can also 26 | # provide some default implementation for these methods. 27 | # 28 | # RU: При необходимости базовый Компонент может объявить интерфейс для 29 | # установки и получения родителя компонента в древовидной структуре. Он 30 | # также может предоставить некоторую реализацию по умолчанию для этих 31 | # методов. 32 | # 33 | # @param [Component] parent 34 | def parent=(parent) 35 | @parent = parent 36 | end 37 | 38 | # EN: In some cases, it would be beneficial to define the child-management 39 | # operations right in the base Component class. This way, you won't need to 40 | # expose any concrete component classes to the client code, even during the 41 | # object tree assembly. The downside is that these methods will be empty for 42 | # the leaf-level components. 43 | # 44 | # RU: В некоторых случаях целесообразно определить операции управления 45 | # потомками прямо в базовом классе Компонент. Таким образом, вам не нужно 46 | # будет предоставлять конкретные классы компонентов клиентскому коду, даже во 47 | # время сборки дерева объектов. Недостаток такого подхода в том, что эти 48 | # методы будут пустыми для компонентов уровня листа. 49 | # 50 | # @abstract 51 | # 52 | # @param [Component] component 53 | def add(component) 54 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 55 | end 56 | 57 | # @abstract 58 | # 59 | # @param [Component] component 60 | def remove(component) 61 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 62 | end 63 | 64 | # EN: You can provide a method that lets the client code figure out 65 | # whether a component can bear children. 66 | # 67 | # RU: Вы можете предоставить метод, который позволит клиентскому коду 68 | # понять, может ли компонент иметь вложенные объекты. 69 | # 70 | # @return [Boolean] 71 | def composite? 72 | false 73 | end 74 | 75 | # EN: The base Component may implement some default behavior or leave it 76 | # to concrete classes (by declaring the method containing the behavior as 77 | # "abstract"). 78 | # 79 | # RU: Базовый Компонент может сам реализовать некоторое поведение по 80 | # умолчанию или поручить это конкретным классам, объявив метод, содержащий 81 | # поведение абстрактным. 82 | # 83 | # @abstract 84 | # 85 | # @return [String] 86 | def operation 87 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 88 | end 89 | end 90 | 91 | # EN: The Leaf class represents the end objects of a composition. A leaf can't 92 | # have any children. 93 | # 94 | # Usually, it's the Leaf objects that do the actual work, whereas Composite 95 | # objects only delegate to their sub-components. 96 | # 97 | # RU: Класс Лист представляет собой конечные объекты структуры. Лист не может 98 | # иметь вложенных компонентов. 99 | # 100 | # Обычно объекты Листьев выполняют фактическую работу, тогда как объекты 101 | # Контейнера лишь делегируют работу своим подкомпонентам. 102 | class Leaf < Component 103 | # return [String] 104 | def operation 105 | 'Leaf' 106 | end 107 | end 108 | 109 | # EN: The Composite class represents the complex components that may have 110 | # children. Usually, the Composite objects delegate the actual work to their 111 | # children and then "sum-up" the result. 112 | # 113 | # RU: Класс Контейнер содержит сложные компоненты, которые могут иметь 114 | # вложенные компоненты. Обычно объекты Контейнеры делегируют фактическую 115 | # работу своим детям, а затем «суммируют» результат. 116 | class Composite < Component 117 | def initialize 118 | @children = [] 119 | end 120 | 121 | # EN: A composite object can add or remove other components (both simple or 122 | # complex) to or from its child list. 123 | # 124 | # RU: Объект контейнера может как добавлять компоненты в свой список вложенных 125 | # компонентов, так и удалять их, как простые, так и сложные. 126 | 127 | # @param [Component] component 128 | def add(component) 129 | @children.append(component) 130 | component.parent = self 131 | end 132 | 133 | # @param [Component] component 134 | def remove(component) 135 | @children.remove(component) 136 | component.parent = nil 137 | end 138 | 139 | # @return [Boolean] 140 | def composite? 141 | true 142 | end 143 | 144 | # EN: The Composite executes its primary logic in a particular way. It 145 | # traverses recursively through all its children, collecting and summing 146 | # their results. Since the composite's children pass these calls to their 147 | # children and so forth, the whole object tree is traversed as a result. 148 | # 149 | # RU: Контейнер выполняет свою основную логику особым образом. Он проходит 150 | # рекурсивно через всех своих детей, собирая и суммируя их результаты. 151 | # Поскольку потомки контейнера передают эти вызовы своим потомкам и так 152 | # далее, в результате обходится всё дерево объектов. 153 | # 154 | # @return [String] 155 | def operation 156 | results = [] 157 | @children.each { |child| results.append(child.operation) } 158 | "Branch(#{results.join('+')})" 159 | end 160 | end 161 | 162 | # EN: The client code works with all of the components via the base interface. 163 | # 164 | # RU: Клиентский код работает со всеми компонентами через базовый интерфейс. 165 | # 166 | # @param [Component] component 167 | def client_code(component) 168 | puts "RESULT: #{component.operation}" 169 | end 170 | 171 | # EN: Thanks to the fact that the child-management operations are declared in 172 | # the base Component class, the client code can work with any component, 173 | # simple or complex, without depending on their concrete classes. 174 | # 175 | # RU: Благодаря тому, что операции управления потомками объявлены в базовом 176 | # классе Компонента, клиентский код может работать как с простыми, так и со 177 | # сложными компонентами, вне зависимости от их конкретных классов. 178 | # 179 | # @param [Component] component 180 | # @param [Component] component2 181 | def client_code2(component1, component2) 182 | component1.add(component2) if component1.composite? 183 | 184 | print "RESULT: #{component1.operation}" 185 | end 186 | 187 | # EN: This way the client code can support the simple leaf components... 188 | # 189 | # RU: Таким образом, клиентский код может поддерживать простые 190 | # компоненты-листья... 191 | simple = Leaf.new 192 | puts 'Client: I\'ve got a simple component:' 193 | client_code(simple) 194 | puts "\n" 195 | 196 | # EN: ...as well as the complex composites. 197 | # 198 | # RU: ...а также сложные контейнеры. 199 | tree = Composite.new 200 | 201 | branch1 = Composite.new 202 | branch1.add(Leaf.new) 203 | branch1.add(Leaf.new) 204 | 205 | branch2 = Composite.new 206 | branch2.add(Leaf.new) 207 | 208 | tree.add(branch1) 209 | tree.add(branch2) 210 | 211 | puts 'Client: Now I\'ve got a composite tree:' 212 | client_code(tree) 213 | puts "\n" 214 | 215 | puts 'Client: I don\'t need to check the components classes even when managing the tree:' 216 | client_code2(tree, simple) 217 | -------------------------------------------------------------------------------- /src/builder/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Builder Design Pattern 2 | # 3 | # Intent: Lets you construct complex objects step by step. The pattern allows you 4 | # to produce different types and representations of an object using the same 5 | # construction code. 6 | # 7 | # RU: Паттерн Строитель 8 | # 9 | # Назначение: Позволяет создавать сложные объекты пошагово. Строитель даёт 10 | # возможность использовать один и тот же код строительства для получения разных 11 | # представлений объектов. 12 | 13 | # EN: The Builder interface specifies methods for creating the different parts 14 | # of the Product objects. 15 | # 16 | # RU: Интерфейс Строителя объявляет создающие методы для различных частей 17 | # объектов Продуктов. 18 | # 19 | # @abstract 20 | class Builder 21 | # @abstract 22 | def produce_part_a 23 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 24 | end 25 | 26 | # @abstract 27 | def produce_part_b 28 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 29 | end 30 | 31 | # @abstract 32 | def produce_part_c 33 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 34 | end 35 | end 36 | 37 | # EN: The Concrete Builder classes follow the Builder interface and provide 38 | # specific implementations of the building steps. Your program may have 39 | # several variations of Builders, implemented differently. 40 | # 41 | # RU: Классы Конкретного Строителя следуют интерфейсу Строителя и 42 | # предоставляют конкретные реализации шагов построения. Ваша программа может 43 | # иметь несколько вариантов Строителей, реализованных по-разному. 44 | class ConcreteBuilder1 < Builder 45 | # EN: A fresh builder instance should contain a blank product object, 46 | # which is used in further assembly. 47 | # 48 | # RU: Новый экземпляр строителя должен содержать пустой объект продукта, 49 | # который используется в дальнейшей сборке. 50 | def initialize 51 | reset 52 | end 53 | 54 | def reset 55 | @product = Product1.new 56 | end 57 | 58 | # EN: Concrete Builders are supposed to provide their own methods for 59 | # retrieving results. That's because various types of builders may create 60 | # entirely different products that don't follow the same interface. 61 | # Therefore, such methods cannot be declared in the base Builder interface 62 | # (at least in a statically typed programming language). 63 | # 64 | # Usually, after returning the end result to the client, a builder 65 | # instance is expected to be ready to start producing another product. 66 | # That's why it's a usual practice to call the reset method at the end of 67 | # the `getProduct` method body. However, this behavior is not mandatory, 68 | # and you can make your builders wait for an explicit reset call from the 69 | # client code before disposing of the previous result. 70 | # 71 | # RU: Конкретные Строители должны предоставить свои собственные методы 72 | # получения результатов. Это связано с тем, что различные типы строителей 73 | # могут создавать совершенно разные продукты с разными интерфейсами. 74 | # Поэтому такие методы не могут быть объявлены в базовом интерфейсе 75 | # Строителя (по крайней мере, в статически типизированном языке 76 | # программирования). 77 | # 78 | # Как правило, после возвращения конечного результата клиенту, экземпляр 79 | # строителя должен быть готов к началу производства следующего продукта. 80 | # Поэтому обычной практикой является вызов метода сброса в конце тела 81 | # метода getProduct. Однако такое поведение не является обязательным, вы 82 | # можете заставить своих строителей ждать явного запроса на сброс из кода 83 | # клиента, прежде чем избавиться от предыдущего результата. 84 | # 85 | # @return [Product1] 86 | def product 87 | product = @product 88 | reset 89 | product 90 | end 91 | 92 | def produce_part_a 93 | @product.add('PartA1') 94 | end 95 | 96 | def produce_part_b 97 | @product.add('PartB1') 98 | end 99 | 100 | def produce_part_c 101 | @product.add('PartC1') 102 | end 103 | end 104 | 105 | # EN: It makes sense to use the Builder pattern only when your products are 106 | # quite complex and require extensive configuration. 107 | # 108 | # Unlike in other creational patterns, different concrete builders can produce 109 | # unrelated products. In other words, results of various builders may not 110 | # always follow the same interface. 111 | # 112 | # RU: Имеет смысл использовать паттерн Строитель только тогда, когда ваши 113 | # продукты достаточно сложны и требуют обширной конфигурации. 114 | # 115 | # В отличие от других порождающих паттернов, различные конкретные строители 116 | # могут производить несвязанные продукты. Другими словами, результаты 117 | # различных строителей могут не всегда следовать одному и тому же интерфейсу. 118 | class Product1 119 | def initialize 120 | @parts = [] 121 | end 122 | 123 | # @param [String] part 124 | def add(part) 125 | @parts << part 126 | end 127 | 128 | def list_parts 129 | print "Product parts: #{@parts.join(', ')}" 130 | end 131 | end 132 | 133 | # EN: The Director is only responsible for executing the building steps in a 134 | # particular sequence. It is helpful when producing products according to a 135 | # specific order or configuration. Strictly speaking, the Director class is 136 | # optional, since the client can control builders directly. 137 | # 138 | # RU: Директор отвечает только за выполнение шагов построения в определённой 139 | # последовательности. Это полезно при производстве продуктов в определённом 140 | # порядке или особой конфигурации. Строго говоря, класс Директор необязателен, 141 | # так как клиент может напрямую управлять строителями. 142 | class Director 143 | # @return [Builder] 144 | attr_accessor :builder 145 | 146 | def initialize 147 | @builder = nil 148 | end 149 | 150 | # EN: The Director works with any builder instance that the client code 151 | # passes to it. This way, the client code may alter the final type of the 152 | # newly assembled product. 153 | # 154 | # RU: Директор работает с любым экземпляром строителя, который передаётся 155 | # ему клиентским кодом. Таким образом, клиентский код может изменить 156 | # конечный тип вновь собираемого продукта. 157 | # 158 | # @param [Builder] builder 159 | def builder=(builder) 160 | @builder = builder 161 | end 162 | 163 | # EN: The Director can construct several product variations using the same 164 | # building steps. 165 | # 166 | # RU: Директор может строить несколько вариаций продукта, используя одинаковые 167 | # шаги построения. 168 | 169 | def build_minimal_viable_product 170 | @builder.produce_part_a 171 | end 172 | 173 | def build_full_featured_product 174 | @builder.produce_part_a 175 | @builder.produce_part_b 176 | @builder.produce_part_c 177 | end 178 | end 179 | 180 | # EN: The client code creates a builder object, passes it to the director and 181 | # then initiates the construction process. The end result is retrieved from 182 | # the builder object. 183 | # 184 | # RU: Клиентский код создаёт объект-строитель, передаёт его директору, а затем 185 | # инициирует процесс построения. Конечный результат извлекается из 186 | # объекта-строителя. 187 | 188 | director = Director.new 189 | builder = ConcreteBuilder1.new 190 | director.builder = builder 191 | 192 | puts 'Standard basic product: ' 193 | director.build_minimal_viable_product 194 | builder.product.list_parts 195 | 196 | puts "\n\n" 197 | 198 | puts 'Standard full featured product: ' 199 | director.build_full_featured_product 200 | builder.product.list_parts 201 | 202 | puts "\n\n" 203 | 204 | # EN: Remember, the Builder pattern can be used without a Director class. 205 | # 206 | # RU: Помните, что паттерн Строитель можно использовать без класса Директор. 207 | puts 'Custom product: ' 208 | builder.produce_part_a 209 | builder.produce_part_b 210 | builder.product.list_parts 211 | -------------------------------------------------------------------------------- /src/abstract_factory/conceptual/main.rb: -------------------------------------------------------------------------------- 1 | # EN: Abstract Factory Design Pattern 2 | # 3 | # Intent: Lets you produce families of related objects without specifying their 4 | # concrete classes. 5 | # 6 | # RU: Паттерн Абстрактная Фабрика 7 | # 8 | # Назначение: Предоставляет интерфейс для создания семейств связанных или 9 | # зависимых объектов без привязки к их конкретным классам. 10 | 11 | # EN: The Abstract Factory interface declares a set of methods that return 12 | # different abstract products. These products are called a family and are 13 | # related by a high-level theme or concept. Products of one family are usually 14 | # able to collaborate among themselves. A family of products may have several 15 | # variants, but the products of one variant are incompatible with products of 16 | # another. 17 | # 18 | # RU: Интерфейс Абстрактной Фабрики объявляет набор методов, которые 19 | # возвращают различные абстрактные продукты. Эти продукты называются 20 | # семейством и связаны темой или концепцией высокого уровня. Продукты одного 21 | # семейства обычно могут взаимодействовать между собой. Семейство продуктов 22 | # может иметь несколько вариаций, но продукты одной вариации несовместимы с 23 | # продуктами другой. 24 | # 25 | # @abstract 26 | class AbstractFactory 27 | # @abstract 28 | def create_product_a 29 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 30 | end 31 | 32 | # @abstract 33 | def create_product_b 34 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 35 | end 36 | end 37 | 38 | # EN: Concrete Factories produce a family of products that belong to a single 39 | # variant. The factory guarantees that resulting products are compatible. Note 40 | # that signatures of the Concrete Factory's methods return an abstract 41 | # product, while inside the method a concrete product is instantiated. 42 | # 43 | # RU: Конкретная Фабрика производит семейство продуктов одной вариации. 44 | # Фабрика гарантирует совместимость полученных продуктов. Обратите внимание, 45 | # что сигнатуры методов Конкретной Фабрики возвращают абстрактный продукт, в 46 | # то время как внутри метода создается экземпляр конкретного продукта. 47 | class ConcreteFactory1 < AbstractFactory 48 | def create_product_a 49 | ConcreteProductA1.new 50 | end 51 | 52 | def create_product_b 53 | ConcreteProductB1.new 54 | end 55 | end 56 | 57 | # EN: Each Concrete Factory has a corresponding product variant. 58 | # 59 | # RU: Каждая Конкретная Фабрика имеет соответствующую вариацию продукта. 60 | class ConcreteFactory2 < AbstractFactory 61 | def create_product_a 62 | ConcreteProductA2.new 63 | end 64 | 65 | def create_product_b 66 | ConcreteProductB2.new 67 | end 68 | end 69 | 70 | # EN: Each distinct product of a product family should have a base interface. 71 | # All variants of the product must implement this interface. 72 | # 73 | # RU: Каждый отдельный продукт семейства продуктов должен иметь базовый 74 | # интерфейс. Все вариации продукта должны реализовывать этот интерфейс. 75 | # 76 | # @abstract 77 | class AbstractProductA 78 | # @abstract 79 | # 80 | # @return [String] 81 | def useful_function_a 82 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 83 | end 84 | end 85 | 86 | # EN: Concrete Products are created by corresponding Concrete Factories. 87 | # 88 | # RU: Конкретные продукты создаются соответствующими Конкретными Фабриками. 89 | class ConcreteProductA1 < AbstractProductA 90 | def useful_function_a 91 | 'The result of the product A1.' 92 | end 93 | end 94 | 95 | class ConcreteProductA2 < AbstractProductA 96 | def useful_function_a 97 | 'The result of the product A2.' 98 | end 99 | end 100 | 101 | # EN: Here's the the base interface of another product. All products can 102 | # interact with each other, but proper interaction is possible only between 103 | # products of the same concrete variant. 104 | # 105 | # RU: Базовый интерфейс другого продукта. Все продукты могут взаимодействовать 106 | # друг с другом, но правильное взаимодействие возможно только между продуктами 107 | # одной и той же конкретной вариации. 108 | # 109 | # @abstract 110 | class AbstractProductB 111 | # EN: Product B is able to do its own thing... 112 | # 113 | # RU: Продукт B способен работать самостоятельно... 114 | # 115 | # @abstract 116 | def useful_function_b 117 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 118 | end 119 | 120 | # EN: ...but it also can collaborate with the ProductA. 121 | # 122 | # The Abstract Factory makes sure that all products it creates are of the 123 | # same variant and thus, compatible. 124 | # 125 | # RU: ...а также взаимодействовать с Продуктами A той же вариации. 126 | # 127 | # Абстрактная Фабрика гарантирует, что все продукты, которые она создает, 128 | # имеют одинаковую вариацию и, следовательно, совместимы. 129 | # 130 | # @abstract 131 | # 132 | # @param [AbstractProductA] collaborator 133 | def another_useful_function_b(_collaborator) 134 | raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" 135 | end 136 | end 137 | 138 | # EN: Concrete Products are created by corresponding Concrete Factories. 139 | # 140 | # RU: Конкретные Продукты создаются соответствующими Конкретными Фабриками. 141 | class ConcreteProductB1 < AbstractProductB 142 | # @return [String] 143 | def useful_function_b 144 | 'The result of the product B1.' 145 | end 146 | 147 | # EN: The variant, Product B1, is only able to work correctly with the 148 | # variant, Product A1. Nevertheless, it accepts any instance of 149 | # AbstractProductA as an argument. 150 | # 151 | # RU: Продукт B1 может корректно работать только с Продуктом A1. Тем не менее, 152 | # он принимает любой экземпляр Абстрактного Продукта А в качестве аргумента. 153 | # 154 | # @param [AbstractProductA] collaborator 155 | # 156 | # @return [String] 157 | def another_useful_function_b(collaborator) 158 | result = collaborator.useful_function_a 159 | "The result of the B1 collaborating with the (#{result})" 160 | end 161 | end 162 | 163 | class ConcreteProductB2 < AbstractProductB 164 | # @return [String] 165 | def useful_function_b 166 | 'The result of the product B2.' 167 | end 168 | 169 | # EN: The variant, Product B2, is only able to work correctly with the 170 | # variant, Product A2. Nevertheless, it accepts any instance of 171 | # AbstractProductA as an argument. 172 | # 173 | # RU: Продукт B2 может корректно работать только с Продуктом A2. Тем не 174 | # менее, он принимает любой экземпляр Абстрактного Продукта А в качестве 175 | # аргумента. 176 | # 177 | # @param [AbstractProductA] collaborator 178 | def another_useful_function_b(collaborator) 179 | result = collaborator.useful_function_a 180 | "The result of the B2 collaborating with the (#{result})" 181 | end 182 | end 183 | 184 | # EN: The client code works with factories and products only through abstract 185 | # types: AbstractFactory and AbstractProduct. This lets you pass any factory 186 | # or product subclass to the client code without breaking it. 187 | # 188 | # RU: Клиентский код работает с фабриками и продуктами только через 189 | # абстрактные типы: Абстрактная Фабрика и Абстрактный Продукт. Это позволяет 190 | # передавать любой подкласс фабрики или продукта клиентскому коду, не нарушая 191 | # его. 192 | # 193 | # @param [AbstractFactory] factory 194 | def client_code(factory) 195 | product_a = factory.create_product_a 196 | product_b = factory.create_product_b 197 | 198 | puts product_b.useful_function_b 199 | puts product_b.another_useful_function_b(product_a) 200 | end 201 | 202 | # EN: The client code can work with any concrete factory class. 203 | # 204 | # RU: Клиентский код может работать с любым конкретным классом фабрики. 205 | puts 'Client: Testing client code with the first factory type:' 206 | client_code(ConcreteFactory1.new) 207 | 208 | puts "\n" 209 | 210 | puts 'Client: Testing the same client code with the second factory type:' 211 | client_code(ConcreteFactory2.new) 212 | --------------------------------------------------------------------------------