📜 ⬆️ ⬇️

Design Patterns in Ruby: A Template Method

Introduction


While the software development field is growing, we are constantly trying to grab the latest technology. Fortunately, the craft of writing supported code is language-independent and in this series of posts we will focus on a powerful set of timeless tools: design patterns.



I highly recommend the book Russ Olsen - Design Patterns in Ruby. Our cycle of posts will draw inspiration from there and be a bit of a short squeeze. So, if you like what you read (and I hope so!), The book will be a great continuation.
')
We will look at various design patterns and learn how to apply them. Today's topic is the Template method, the simplest design pattern.

First day of construction


Right tools


Simply put, the design patterns are the tools that follow us to design software. However, like any tools, we must be able to choose the right one for each specific task. Of course we can hammer in the screw with a hammer, but most likely we will damage the boards, the use of the screwdriver would be more appropriate. Before using one of the many design patterns, it is important to understand the problem you are trying to solve.

It is wrong to use a design pattern to solve a problem for which it is not intended. In other words, it is considered bad form to use a pattern for a task that does not require the above design pattern to solve.

Let's build some walls.


Today our foreman told us to build several walls. All walls are the same size and made of the same material (for this design project, the foreman gave us a very simple set of requirements).

#   (Wall) require 'minitest/autorun' describe Wall do let(:wall) { Wall.new } it 'should state its dimensions' do wall.dimensions.must_equal 'I am 30ft. long and 20ft. wide!' end it 'should be made from brick' do wall.made_from.must_equal 'I am made from brick!' end end 

What a good boss, he gave us the blueprints! Now it's small, let's build a wall:

 class Wall def dimensions 'I am 30ft. long and 20ft. wide!' end def made_from 'I am made from brick!' end end 

Fine! Our tests pass, everyone is happy and we are finally going to have lunch!

Hammer or Nails?


When we returned, the foreman said that we needed more walls. "Here is a piece of cake," we said, recalling how easy it was to build a wall ( Wall ).

"Not so fast, guys," hurried to protest the foreman. We have new blueprints with new wall requirements.

 #    (BrickWall) describe BrickWall do let(:brick_wall) { BrickWall.new } it 'should state its dimensions' do brick_wall.dimensions.must_equal 'I am 30ft. long and 20ft. wide!' end it 'should be made from brick' do brick_wall.made_from.must_equal 'I am made from brick!' end end #    (ConcreteWall) describe ConcreteWall do let(:concrete_wall) { ConcreteWall.new } it 'should state its dimensions' do concrete_wall.dimensions.must_equal 'I am 30ft. long and 20ft. wide!' end it 'should be made from concrete' do concrete_wall.made_from.must_equal 'I am made from concrete!' end end #    (WoodWall) describe WoodWall do let(:wood_wall) { WoodWall.new } it 'should state its dimensions' do wood_wall.dimensions.must_equal 'I am 10ft. long and 20ft. wide!' end it 'should be made from wood' do wood_wall.made_from.must_equal 'I am made from wood!' end end 

Hmm ... A few ideas flashed through our heads. We can follow the wall class principles and define each method with a hard-coded output string for the BrickWall , ConcreteWall and WoodWall . It looks like a nice idea, but we will have to hardcode each instance method. What if a house needs a dozen different types of walls?

Open up that little box!


By shaking our afternoon coffee, we realized that there is a good tool for our task - the Pattern method pattern.

Following the Template Method pattern, creating a skeleton class (sceletal class) will lay the foundation for subclasses or concrete classes. With the skeleton class go abstract methods, which in turn can be overridden in subclasses. That is, we define the Wall class (our skeleton class) and its subclasses: BrickWall , ConcreteWall and WoodWall .

After reviewing the drawings, we noticed that all three different classes of walls contain #dimensions and #made_from methods that return slightly different rows. With this in mind, let's implement our wall class and its subclasses.

 class Wall def dimensions "I am #{length}ft. long and #{width}ft. wide!" end def made_from "I am made from #{material}!" end private def length 30 end end class BrickWall < Wall private def width 20 end def material 'brick' end end class ConcreteWall < Wall private def width 20 end def material 'concrete' end end class WoodWall < Wall private def length 10 end def width 20 end def material 'wood' end end 

Discussion


Hook methods


In the Wall class, we define the private method #length because we see that BrickWall and ConcreteWall have the same length. As for the WoodWall class, we simply redefined #length to return the value 10 . This is an example hook method.

Hook methods are used for two purposes:
1) Redefine the skeleton implementation and implement something new.
2) or just use the default implementation.

Note that the default implementation in the skeleton class does not have to be defined. For example, we could have this:

 class Wall ... private def length raise NotImplementedError, 'Sorry, you have to override length' end end class BrickWall < Wall private ... def length 30 end end 

(approx. lane - although this is not the best practice for ruby, more here , section "Never Require Inheritance")

In the example above, the #length class #length method is made as a stub for #lenght in BrickWall , a particular class. In essence, the hook method informs all concrete classes that the method should be overridden. If the base implementation is not defined, then subclasses are required to implement hook methods.

Such are the good walls


Our foreman is fascinated by the results of labor, and perhaps we will end this today. As we have seen, using the Template Method pattern is not at all difficult. To begin with, we created a base class in which we defined the necessary hook methods that can be redefined in our subclasses. Of course, this particular design pattern does not solve any conceivable problem, but it helps to keep our code clean with inheritance.

Next we discuss the Strategy method. Stay in touch!

Source: https://habr.com/ru/post/188046/


All Articles