[GO] Read design patterns in Ruby

Chapter 1 Good Programs and Patterns

Pattern for pattern

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.

1. Separate from what does not change than what changes

All changes in software development are local, so you shouldn't have to go through all the code.

2. Program to the interface, not to the implementation

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.

3. Aggregate from inheritance

4. Delegation, delegation, delegation

Write a method to transfer responsibility to the aggregated object. It is more flexible than inheritance and has no side effects.

Sample code

Code example with inheritance

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

Code example with aggregation and delegation

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

Don't make until you need it

YAGNI = You Ain't Gonna Need It

14 patterns of Gof covered in this book

  1. Template Method
  2. Strategy object
  3. Observer pattern
  4. Composite pattern
  5. Iterator pattern
  6. Command pattern
  7. Adapter pattern
  8. Proxy
  9. Decorator pattern
  10. Singleton
  11. Factory Method
  12. Abstract Factory
  13. Builder pattern
  14. Interpreter

Patterns in Ruby

  1. Internal domain specific language
  2. Metaprogramming
  3. Convention over Configuration(CoC)

Chapter 3: Modifying the algorithm: Template Method

Template Method The general idea of ​​patterns

Hook method

Duck typing

Ruby does not check in the language that the passed object belongs to a particular class.

Sample code

# 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

Chapter 4: Replacing Algorithms: Stragtegy

Inheritance-based techniques, such as the Template Method pattern, have limited flexibility because they result in a dependency on the superclass.

Delegation, delegation, and even delegation

Proc and block

Code block-based strategy

The sample code example can be simplified by changing it as follows.

  1. The initialize method of the Report class receives a block of code.
  2. Change the method that Report # output_report calls from output_report to call.
  3. Instead of creating an instance of the * Formatter class, create a Proc object.

Code block-based strategies are effective in the following cases.

Sample code

# 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

Chapter 5 Follow Changes: Obsever

Sample code

# 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!

Chapter 6 Assemble the whole from the part: Composite

Sample code

#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

Chapter 7 Manipulating Collection: Iterator

Sample code

#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}") }

Chapter 8 Executing Instructions: Command

Sample code

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

Chapter 9 Bridging the Gap: Adapter

#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)

It's getting annoying, so it's complete

And continue.

Recommended Posts

Read design patterns in Ruby
Various design patterns
Class in Ruby
Java Design Patterns
Heavy in Ruby! ??
I read "7 Design Patterns to Refactor MVC Components in Rails" (writing)
Read JSON in Java
About eval in Ruby
Introduction to design patterns (introduction)
Output triangle in Ruby
Ruby: Read CouchDB data (Read)
Variable type in ruby
Fast popcount in Ruby
Study GoF design patterns
Read mp3 tag (ID3v1) in Ruby (no library used)
I read Hiroshi Yuki "Introduction to Design Patterns Learned in Java Language" (SB Creative)
ABC177 --solving E in Ruby
Validate JWT token in Ruby
Implemented XPath 1.0 parser in Ruby
Read binary files in Java 1
Write class inheritance in Ruby
Update Ruby in Unicorn environment
Introduction to Design Patterns (Composite)
Integer unified into Integer in Ruby 2.4
[Ruby] Exception handling in functions
Use ruby variables in javascript.
Read standard input in Java
Multiplication in a Ruby array
About regular expressions in Ruby
Introduction to design patterns (Flyweight)
Read binary files in Java 2
I read module-info.java in java.base
Introduction to design patterns Prototype
Birthday attack calculation in Ruby
Introduction to Design Patterns (Iterator)
Judgment of fractions in Ruby
Find Roman numerals in Ruby
Try using gRPC in Ruby
Read "Object-Oriented Design Practice Guide"
[Ruby] Find numbers in arrays
Why design patterns are needed
[Java] Summary of design patterns
NCk mod p in Ruby
Chinese Remainder Theorem in Ruby
Introduction to Design Patterns (Strategy)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 10)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 7)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 3)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 9)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 6)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 4)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 5)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 2)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 1)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 11)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 12)
Summary of "Design Patterns Learned in Java Language (Multithread Edition)" (Part 8)
Sorting hashes in a Ruby array
Ruby design pattern template method pattern memo
[Ruby on Rails] Read try (: [],: key)
Basics of sending Gmail in Ruby