Week 4 - Object-Oriented Design (Encapsulation & Cohesion, Inheritance & Module)
Encapsulation
We break our programs up into multiple objects to control complexity. So how do we decide which methods and variables should live in which objects?
Example 1
Bank
get_balance
credit_account(date, amount)
debit_account(date, amount)
print_statement
Can be turned into two groups:
get_balance
print_statement
#
credit_account(date, amount)
debit_account(date, amount)
Example 2
Reorganising secret diary into classes:
SecretDiary
- lock
- unlock
- add_entry
- get_entries
require 'secretDiary'
class Diary
def initialize
@secretDiary = SecretDiary.new
end
def lock
@secretDiary.lock
end
def unlock
@secretDiary.unlock
end
def add_entry
if @secretDiary.locked
raise 'Diary is locked!'
else
'Add in entry'
end
end
def get_entries
if @secretDiary.locked
raise 'Diary is locked!'
else
'Show entries'
end
end
end
class SecretDiary
attr_accessor :locked
def initialize
@locked = true
end
def unlock
@locked = false
end
def lock
@locked = true
end
end
Low Coupling
How much do your different modules depend on each other?
Modules should be as independent as possible from other modules, so that changes to module don’t heavily impact other modules.
High coupling would mean that your module knows the way too much about the inner workings of other modules. Modules that know too much about other modules make changes hard to coordinate and make modules brittle. If Module A knows too much about Module B, changes to the internals of Module B may break functionality in Module A.
By aiming for low coupling, you can easily make changes to the internals of modules without worrying about their impact on other modules in the system. Low coupling also makes it easier to design, write, and test code since our modules are not interdependent on each other. We also get the benefit of easy to reuse and compose-able modules. Problems are also isolated to small, self-contained units of code.
High Cohesion
We want to design components that are self-contained: independent, and with a single, well-defined purpose—
Cohesion often refers to how the elements of a module belong together. Related code should be close to each other to make it highly cohesive.
Easy to maintain code usually has high cohesion. The elements within the module are directly related to the functionality that module is meant to provide. By keeping high cohesion within our code, we end up trying DRY code and reduce duplication of knowledge in our modules. We can easily design, write, and test our code since the code for a module is all located together and works together.
Low cohesion would mean that the code that makes up some functionality is spread out all over your code-base. Not only is it hard to discover what code is related to your module, it is difficult to jump between different modules and keep track of all the code in your head.
Cohesion
Each class should have one purpose or job, sometimes referred to as its responsibility.
A class has high cohesion when everything inside of it relates to that purpose, without anything extraneous. Perfection is achieved when there is nothing left to take away.
Maintainable Code
Writing maintainable code helps increase productivity for developers. Having highly maintainable code makes it easier to design new features and write code. Modular, component-based, and layered code increases productivity and reduces risk when making changes.
By keeping code loosely coupled, we can write code within one module without impacting other modules. And by keeping code cohesive, we make it easier to write DRY code that is easy to work with.
A good way to determine how cohesive and coupled your code is, is illistrated by this quote from The Pragmatic Programmer:
When you come across a problem, assess how localized the fix is. Do you change just one module, or are the changes scattered throughout the entire system? When you make a change, does it fix everything, or do other problems mysteriously arise?
While you are writing and working with your code base, ask yourself:
- How many modules am I touching to fix this or create this functionality?
- How many different places does this change need to take place?
- How hard is it to test my code?
- Can we improve this by making code more loosely coupled? Can this be improved by making our code more cohesive?
Inheritance
Classes can inherit behaviour from another class.
All fruits have a color, a weight, and a name.
Some fruits may have special characteristics that aren't shared with other fruits, so you create a new class that inherits the characteristics of ALL fruits (color, weight, etc.) & then you add the special characteristic.
class Pet
attr_accessor :name, :age
def initialize name, age
@name = name
@age = age
end
class Dog < Pet ##we use the '<' operator to show inheritance
attr_reader :dog_breed
def initialize dog_breed
@dog_breed = dog_breed
end
Pet is initialized with a name and age, as they are attributes all pets will share. The attr_accessor is there to read and write the values of each attribute for us. By using “<” we are essentially saying that our class “Dog” is inheriting from our “Pet” class. This is what establishes the parent-child relationship. Pet is passing all of its methods and attributes to Dog and Cat, but our child classes have their own properties such as breed.
Module
Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits:
- Modules provide a namespace and prevent name clashes.
- Modules implement the mixin facility.
Ruby modules allow you to create groups of methods that you can then include or mix into any number of classes. Modules only hold behaviour, unlike classes, which hold both behaviour and state.
In order to include a module into a class, we use the method include which takes one parameter - the name of a Module.
module WarmUp
def push_ups
"Phew, I need a break!"
end
end
class Gym
include WarmUp
def preacher_curls
"I'm building my biceps."
end
end
class Dojo
include WarmUp
def tai_kyo_kyu
"Look at my stance!"
end
end
puts Gym.new.push_ups
puts Dojo.new.push_ups