Learn Object Oriented Programming

Converting procedural style code to Object Oriented Code

Let's say that we need to produce a message as shown below.

1Dear Mike,
2
3Thank you for choosing the monthly plan of $19/month.
4
5Please note that we also provide annual plan of $190/year which
6you can upgrade to at anytime.
7
8If you need any help then please contact us.
9- neetoChat team

In the above case the amount and the currency might change.

We can solve this by writing code like this.

1currency = "$"
2monthly_price = 19
3annual_price = 190
4
5greetings = "Dear Mike,"
6line1 = "Thank you for choosing the monthly plan of #{currency}#{monthly_price}/month."
7
8line2 = "Please note that we also provide annual plan of #{currency}#{annual_price}/year"
9line2 << " which you can upgrade to at anytime."
10
11line3 = "If you need any help then please contact us."
12line4 = "- neetoChat team"
13
14puts greetings
15puts line1
16puts line2
17puts line3
18puts line44

As we have seen before this style of writing code is called "Procedural style of programming".

Let's take a look at how to solve the same problem using "Object Oriented style of programming". We will start with creating a class called Price.

1class Price
2end

Currently this class has nothing. Let's move all the lines from procedural to object oriented style.

1class Price
2  def self.greetings
3    "Dear Mike,"
4  end
5
6  def self.line1(currency, monthly_price)
7    "Thank you for choosing the monthly plan of #{currency}#{monthly_price}/month."
8  end
9
10  def self.line2(currency, annual_price)
11    "Please note that we also provide annual plan of #{currency}#{annual_price}/year" <<
12    " which you can upgrade to at anytime."
13  end
14
15  def self.line3
16    "If you need any help then please contact us."
17  end
18
19  def self.line4
20    "- neetoChat team"
21  end
22end
23
24currency = "$"
25monthly_price = 19
26annual_price = 190
27
28puts Price.greetings
29puts Price.line1(currency, monthly_price)
30puts Price.line2(currency, monthly_price)
31puts Price.line3
32puts Price.line4

We created a few "class methods" and moved the procedural code there.

For methods "line1" and "line2" we need to pass currency to both the methods. why do we need to pass same currency two times. We need to do that here because the class Price does not hold currency.

Rather than passing currency twice we will let the class hold the value of currency and then use it when we need currency.

In Ruby when we instantiate a class then initialize method is called. That's a good place to pass any state level information for that class.

1class Price
2  def initialize(currency)
3    @currency = currency
4  end
5 end

Now the instantiated object will hold the state called currency which can be used by other methods. Also we need to change methods from class methods to instance methods. After the change the class looks like this.

1class Price
2  def initialize(currency)
3    @currency = currency
4  end
5
6  def greetings
7    "Dear Mike,"
8  end
9
10  def line1(monthly_price)
11    "Thank you for choosing the monthly plan of #{@currency}#{monthly_price}/month."
12  end
13
14  def line2(annual_price)
15    "Please note that we also provide annual plan of #{@currency}#{annual_price}/year" <<
16    " which you can upgrade to at anytime."
17  end
18
19  def line3
20    "If you need any help then please contact us."
21  end
22
23  def line4
24    "- neetoChat team"
25  end
26end
27
28currency = "$"
29monthly_price = 19
30annual_price = 190
31
32price = Price.new(currency)
33puts price.greetings
34puts price.line1(monthly_price)
35puts price.line2(annual_price)
36puts price.line3
37puts price.line4

In the above case methods "line1" and "line2" are reaching out to "@currency". This is not ideal. A class should be able to control what is available and what is not.

In this case we can use attr_reader to make currency available to all the methods. Note these methods need to use currency and not @currency.

Here is the modified solution.

1class Price
2
3  attr_reader :currency
4
5  def initialize(currency)
6    @currency = currency
7  end
8
9  def greetings
10    "Dear Mike,"
11  end
12
13  def line1(monthly_price)
14    "Thank you for choosing the monthly plan of #{currency}#{monthly_price}/month."
15  end
16
17  def line2(annual_price)
18    "Please note that we also provide annual plan of #{currency}#{annual_price}/year" <<
19    " which you can upgrade to at anytime."
20  end
21
22  def line3
23    "If you need any help then please contact us."
24  end
25
26  def line4
27    "- neetoChat team"
28  end
29end
30
31currency = "$"
32monthly_price = 19
33annual_price = 190
34
35price = Price.new(currency)
36puts price.greetings
37puts price.line1(monthly_price)
38puts price.line2(annual_price)
39puts price.line3
40puts price.line4

So far we have been instantiating an object and on that object we have been invoking all these methods. However we can move the call to these methods to inside the class and have only one method from outside to call it.

Here is a modified solution.

1class Price
2
3  attr_reader :currency
4
5  def initialize(currency)
6    @currency = currency
7  end
8
9  def message(monthly_price, annual_price)
10    msg = []
11    msg << greetings
12    msg << line1(monthly_price)
13    msg << line2(annual_price)
14    msg << line3
15    msg << line4
16    msg.each { |m| puts m }
17  end
18
19  private
20
21  def greetings
22    "Dear Mike,"
23  end
24
25  def line1(monthly_price)
26    "Thank you for choosing the monthly plan of #{currency}#{monthly_price}/month."
27  end
28
29  def line2(annual_price)
30    "Please note that we also provide annual plan of #{currency}#{annual_price}/year" <<
31    " which you can upgrade to at anytime."
32  end
33
34  def line3
35    "If you need any help then please contact us."
36  end
37
38  def line4
39    "- neetoChat team"
40  end
41end
42
43currency = "$"
44monthly_price = 19
45annual_price = 190
46
47price = Price.new(currency)
48price.message(monthly_price, annual_price)

In the above case while calling the method message we are passing "monthly_price" and "annual_price". We can pass on the pricing information when we are instantiating the class. Let's do that.

Here is the modified code.

1class Price
2
3  attr_reader :currency, :monthly_price, :annual_price
4
5  def initialize(currency, monthly_price, annual_price)
6    @currency = currency
7    @monthly_price = monthly_price
8    @annual_price = annual_price
9  end
10
11  def message
12    msg = []
13    msg << greetings
14    msg << line1
15    msg << line2
16    msg << line3
17    msg << line4
18    msg.each { |m| puts m }
19  end
20
21  private
22
23  def greetings
24    "Dear Mike,"
25  end
26
27  def line1
28    "Thank you for choosing the monthly plan of #{currency}#{monthly_price}/month."
29  end
30
31  def line2
32    "Please note that we also provide annual plan of #{currency}#{annual_price}/year" <<
33    " which you can upgrade to at anytime."
34  end
35
36  def line3
37    "If you need any help then please contact us."
38  end
39
40  def line4
41    "- neetoChat team"
42  end
43end
44
45currency = "$"
46monthly_price = 19
47annual_price = 190
48price = Price.new(currency, monthly_price, annual_price)
49price.message
    ⌘F
      to navigateEnterto select Escto close
      Older
      Newer