To summarize the idea of [Gof Design Patterns](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2), there are four points.
All changes in software development are local, so you shouldn't have to go through all the code.
The interface here is not an abstract interface in Java or C #, but a general type as much as possible.
Example) "Vehicles" for airplanes, trains, and automobiles
As a result of programming for the interface, Coupling is reduced, and the code is strong against changes, requiring only a few class changes.
Write a method to transfer responsibility to the aggregated object. It is more flexible than inheritance and has no side effects.
class Vehicle
# ...
def start_engine
#Start the engine
end
def stop_engine
#Stop the engine
end
end
class Car < Vehicle
def sunday_drive
start_engine
#I will go to the countryside and come back.
stop_engine
end
end
class Engine
def start
#Start the engine
end
def stop
#Stop the engine
end
end
class Car
def initialize
@engine = Engine.new
end
def sunday_drive
@engine.start
#I will go to the countryside and come back.
@engine.stop
end
def start_engine
@engine.start
end
def stop_engine
@engine.stop
end
end
YAGNI = You Ain't Gonna Need It
Ruby does not check in the language that the passed object belongs to a particular class.
# NOTE:I'm using a one-line method because it's long
#Abstract class
class Report
def initialize
@title = 'Monthly report'
@text = ['Good', 'Best condition']
end
def output_report
output_start
output_head
output_body_start
output_body
output_body_end
output_end
end
def output_body
@text.each do |line|
output_line(line)
end
end
def output_start; end
def output_head; output_line(@title) end
def output_body_start; end
def output_line(line)
raise 'Called abstract method: output_line'
end
def output_body_end; end
def output_end; end
end
#Concrete class 1
class HTMLReport < Report
def output_start; puts('<html>') end
def output_head
puts(' <head>')
puts(" <title>#{@title}</title>")
puts('</head>')
end
def output_body_start; puts('body') end
def output_line(line); puts(" <p>#{line}</p>") end
def output_body_end; puts('</body>') end
def output_end; puts('</html>') end
end
#Concrete class 2
class PlainTextReport < Report
def output_head
puts("**** #{@title} ****")
puts
end
def output_line(line); puts(line) end
end
Inheritance-based techniques, such as the Template Method pattern, have limited flexibility because they result in a dependency on the superclass.
The sample code example can be simplified by changing it as follows.
Code block-based strategies are effective in the following cases.
# Context
class Report
attr_reader :title, :text
attr_accessor :formatter
def initialize(formatter)
@title = 'Monthly report'
@text = ['Good', 'Best condition']
@formatter = formatter
end
def output_report
@formatter.output_report(@title, @text)
end
end
# Strategy1
class HTMLFormatter < Formatter
def output_report(title, text)
puts('<html>')
puts(' <head>')
puts(" <title>#{title}</title>")
puts(' </head>')
puts(' <body>')
text.each do |line|
puts(" <p>#{line}</p>")
end
puts(' </body>')
puts('</html>')
end
end
# Strategy2
class PlainTextFormatter < Formatter
def output_report(title, text)
puts("***** #{title} *****")
text.each do |line|
puts(line)
end
end
end
# # How to use Strategy
# report = Report.new(HTMLFormatter.new)
# report.output_report
#
# report.formatter = PlainTextFormatter.new
# report.output_report
Observer pattern
The idea of creating a clean interface between the originator and the consumer of the news that "some object has changed".
When an object is interested in receiving notification of the state of the Subject, register the object with the Subject as an Observer.
Subject (Observable object)
A class that has news.
In the Observer pattern, the Subject does most of the notification work, and it is the Subject who is responsible for keeping track of the Observer.
Observer
Objects that are interested in getting news.
# Subject
module Subject
def initialize
@observeres = []
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
def notify_observers
@obsevers.each do |observer|
observer.update(self)
end
end
end
class Employee
include Subject
attr_accessor :name, :title, :salary
def initialize(name, title, salary)
super()
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
# Observer
class Payroll
def update(changed_employee)
puts("#{changed_employee.name}Write a check for!")
puts("His salary is now#{changed_employee.salary}is!")
end
end
# # How to use Observer
# fred = Employee.new('Fred', 'Crane Operator', 30000.0)
# payroll = Payroll.new
# fred.add_observer(payroll)
# fred.salary = 35000.0
# =>Write a check for Fred!
#His salary is now 35,000.It's 0!
Composite pattern
A design pattern that represents the situation where "the whole behaves like a part".
If a complex object shares the characteristics of an individual component, that is, if the whole is very similar to that part, the Composite pattern fits well.
Used when you want to create a hierarchical or tree-structured object.
When you don't want the tree-based code to wonder if it's dealing with a simple object or an entire cluttered branch.
Component class
A common interface or base class for all objects.
A place to manage parent-child relationships.
Leaf class
A simple component of the process, one or more required. Implement the interface.
It is better not to handle child objects.
Composite class
It has a role as a Component and also a role as a collection of components.
A higher level object created from a subcomponent.
Requires a method to add or remove child objects.
#Component class
class Task
attr_reader :name, :parent
def initialize(name)
@name = name
@parent = nil
end
def get_time_required
0.0
end
end
#Leaf class 1
class AddIngredientTask < Task
def initialize
super('Add dry ingredients')
end
def get_time_required
1.0 #1 minute to add flour and sugar
end
end
#Leaf class 2
class MixTask < Task
def initialize
super('Mix that batter up')
end
def get_time_required
3.0 #3 minutes to mix
end
end
#Composite class
class CompositeTask < Task
def initialize(name)
super(name)
@sub_tasks = []
end
def add_sub_task(task)
@sub_tasks << task
task.parent = self
end
def remove_sub_task(task)
@sub_tasks.delete(task)
task.parent = nil
end
def get_time_required
time = 0.0
@sub_tasks.each { |task| time += task.get_time_required }
time
end
end
class MakeBatterTask < CompositeTask
def initialize
super('Make batter')
add_sub_task(AddDryIngredientTask.new)
add_sub_task(AddLiquidTask.new)
add_sub_task(MixTask.new)
end
Iterator pattern
Provides a way to access the elements of an aggregate object in sequence without exposing the underlying internal representation.
External iterator
The iterator is a separate object from ** Aggregate ** (the object assigned to the array formal argument in the example below).
The client drives the iteration and does not call the next until the next element is ready.
Because it is external, it can be shared and passed to other methods and objects.
Requires an iterator object.
Example: IO class file operation
Internal iterator
Iterators use code blocks to convey logic to aggregates.
Aggregate objects can call code blocks for each child object.
Simple and easy to understand code.
No separate iterator object required.
Example: Array, String, Enumerable, Hash class
#External iterator
class ArrayIterator
def initialize(array)
@array = array
@index = 0
end
def has_next?
@index < @array.length
end
def item
@array[@index]
end
def next_item
value = @array[@index]
@index += 1
value
end
end
#Internal iterator
#* Actually, the Array class has an iterator method called each, so use that.
def for_each_element(array)
i = 0
while i < array.length
yield(array[i])
i += 1
end
end
# #How to use internal iterator
# a = [10, 20, 30]
# for_each_element(a) { |element| puts("The element is #{element}") }
class Command
attr_accessor :description
def initialize(description)
@description = description
end
def execute
end
end
class CreateFile < Command
def initialize(path, contents)
super("Create file: #{path}")
@path = path
@contents = contents
end
def execute
f = File.open(@path, "w")
f.write(@contents)
f.close
end
def unexecute
File.delete(@path)
end
end
class DeleteFile < Command
def initialize(path)
super("Delete file: #{path}")
@path = path
end
def execute
File.delete(@path)
end
def unexecute
if @contents
f = File.open(@path, "w")
f.write(@contents)
f.close
end
end
end
class CopyFile < Command
def initialize(source, target)
super("Copy file: #{source} to #{target}")
@source = source
@target = target
end
def execute
FileUtils.copy(@source, @target)
end
end
# Composite
class CompositeCommand < Command
def initialize
@commands = []
end
def add_command(cmd)
@command << cmd
end
def execute
@commands.each { |cmd| cmd.execute }
end
def unexecute
@commands.reverse.each { |cmd| cmd.unexecute }
end
def description
description = ''
@commands.each { |cmd| description += cmd.description + "\n" }
description
end
end
# # How to use Command
# cmds = CompositeCommand.new
# cmds.add_command(CreateFile.new('file1.txt', "hello world\n"))
# cmds.add_command(CopyFile.new('file1.txt', "file2.txt"))
# cmds.add_command(DeleteFile.new(''file1.txt'))
# cmds.execute
#Encrypter is a client object.
#The client actually has a reference to the Adapter, StringIOAdapter.
#The StringIOAdapter class looks like a normal IO object to the outside world.
#However, the StringIOAdapter class gets a character from string, which is an adaptee.
class Encrypter
def initialize(key)
@key = key
end
def encrypt(reader, writer)
key_index = 0
while not reader.eof?
clear_char = reader.getc
encrypted_char = clear_char ^ @key[key_index]
writer.putc(encrypted_char)
key_index = (key_index + 1) % @key.size
end
end
end
class StringIOAdapter
def initialize(string)
@string = string
@position = 0
end
def getc
if @position >= @string.length
raise EOFError
end
ch = @string[@position]
@position += 1
return ch
end
def eof?
return @position >= @string.length
end
end
# encrypter = Encrypter.new('XYZZY')
# reader = StringIOAdapter.new('We attack at dawn')
# writer = File.open('out.txt', 'w')
# encrypter.encrypt(reader, writer)
Proxy object: Proxy
Improve objects: Decorator
Only Guarantee: Singleton
Pick the right class: Factory
Make objects easier to assemble: Builder
Assemble in a dedicated language: Interpreter
Create your own language: Domain-Specific Languages
Create custom objects: metaprogramming
Convention over Configuration
And continue.
Recommended Posts