I believe it is only with experience that you learn if applying a design pattern would be beneficial or not. And at what stage it should be considered. For instance, if writing a piece of code, you can see which pattern your code is following, and then apply it. Rather than thinking that I will write a piece of code using an X pattern.
“Design patterns can be extremely helpful in crafting beautiful code, but the way in which they’re applied often determines their usefulness. Applying a design pattern in the wrong scenario can push you into a corner, ultimately leading to more disarray than would have been present if it weren’t for the design pattern. I’m going to pick on the singleton pattern a bit. (Pack, 2012)”
Pack (2012) discusses a situation where “applying a design pattern” can be discouraging.
“Let’s create a file system singleton that writes some text to /dev/null:
require ‘singleton’
class DevNullSingleton
includeSingleton
def write(text)
File.open(‘/dev/null’, ‘w’) do |file|
file.write text
end
end
end
We can use the singleton by referencing its instance:
DevNullSingleton.instance.write(‘Something to dev null’)
Realistically, this has the same semantics as setting a global constant if we didn’t want to use Ruby’s singleton library:
DEV_NULL = DevNullSingleton.new
# … later in the code …
DEV_NULL.write(‘Something to dev null’)
So, now our application grows, and we need another file system writer that outputs to /tmp. We’re posed with a few options.
We can rename our singleton and allow the #write method to accept a path. The API looks like this:
FileSystemSingleton.instance.write(‘/dev/null’, ‘Something to dev null’)
FileSystemSingleton.instance.write(‘/tmp’, ‘Something to tmp’)
This is bad. If we want to write numerous things to /dev/null, we have a large degree of duplication:
FileSystemSingleton.instance.write(‘/dev/null’, ‘Something to dev null’)
FileSystemSingleton.instance.write(‘/dev/null’, ‘Something ele to dev null’)
FileSystemSingleton.instance.write(‘/dev/null’, ‘Another thing to dev null’)
Alternatively, we can create a new singleton class that writes to /tmp:
class TmpSingleton
includeSingleton
def write(text)
# …
end
end
But now, every time we want to write to a different location on the file system, we need to create a new singleton class. Not great, either.
Probably, the better option is to break ties with the singleton and start instantiating classes normally:
class FileSystem
def initialize(path)
@path = path
end
def write(text)
File.open(@path, ‘w’) do |file|
file.write text
end
end
end
Now, when we want to write multiple times to /dev/null, we instantiate only once and use it as we would any other class:”
dev_null = FileSystem.new(‘/dev/null’)
dev_null.write(‘Something to dev null’)
dev_null.write(‘Something ele to dev null’)
dev_null.write(‘Another thing to dev null’)
References:
Pack, M. (2012) The First Step to Applying Design Patterns: Don’t. Available at: http://mikepackdev.com/blog_posts/36-the-first-step-to-applying-design-patterns-don-t (Accessed: 20 September 2017)