├── .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 |
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 |
--------------------------------------------------------------------------------