Example parsing: Use of Strategy Mode in Ruby Design Mode Programming, rubystrategy

Source: Internet
Author: User

Example analysis of the use of Strategy strategy pattern in Ruby design pattern programming, rubystrategy

Today your leader is eager to find you, I hope you can help him a little, he is anxious to go to a meeting now. What can I do for you? You are curious.
He said to you that currently there is a user information table in the database of your project, which contains the data of users, and now you need to complete a function of selectively querying user information. He said that he will pass you an array of many user names, and you need to find out their corresponding data according to these user names.
This function is very simple, you readily agreed. Since your project uses a MySQL database, you quickly wrote the following code:

require 'mysql'
 
class QueryUtil
  def find_user_info usernames
    @db = Mysql.real_connect ("localhost", "root", "123456", "test", 3306);
    sql = "select * from user_info where"
    usernames.each do | user |
      sql << "username = '"
      sql << user
      sql << "'or"
    end
    puts sql
    result = @ db.query (sql);
    result.each_hash do | row |
      #Process the data read from the database
    end
    #The data read should be assembled into an object and returned, and omitted here
  ensure
    @ db.close
  end
end
Here the SQL statement is assembled according to the passed in username array, and then the corresponding row is searched in the database. For debugging, you also print out the assembled SQL statement.
Then, you wrote the following code to test this method:

qUtil = QueryUtil.new
qUtil.find_user_info ["Tom", "Jim", "Anna"]
Now run the test code and you find that the program went wrong. So you immediately checked the printed SQL statement and found that there was a problem.

select * from user_info where username = 'Tom' or username = 'Jim' or username = 'Anna' or
The assembled SQL statement adds an or keyword at the end! Because the for loop should not add or when the last data is executed, but the code is very clumsy to add the or keyword to the last data, resulting in an error in the syntax of the SQL statement.
what should I do?
Got it! You flashed light and came up with a solution. After the SQL statement is assembled, isn't it good to intercept the position before the last or. So you change the code to look like this:

require 'mysql'
 
class QueryUtil
  def find_user_info usernames
    @db = Mysql.real_connect ("localhost", "root", "123456", "test", 3306);
    sql = "select * from user_info where"
    usernames.each do | user |
      sql << "username = '"
      sql << user
      sql << "'or"
    end
    sql = sql [0 ..-"or" .length]
    puts sql
    result = @ db.query (sql);
    result.each_hash do | row |
      #Process the data read from the database
    end
    #The data read should be assembled into an object and returned, and omitted here
  ensure
    @ db.close
  end
end
Use String's interception substring method, only the part before the last or is run, and then run the test code, everything is normal, the printed SQL statement is as follows:

select * from user_info where username = 'Tom' or username = 'Jim' or username = 'Anna'
Okay, finished! You are confident.
After your leader has finished the meeting, come and see your results. Overall, he is quite satisfied, but he always feels a bit wrong about the SQL statement assembly algorithm you use, but it is not that bad. So he told you another algorithm for assembling SQL statements, letting you add it to the code, but don't delete the previous algorithm, keep it first, and then he ran away very busy. So, you add the algorithm he just taught you, the code is as follows:

require 'mysql'
 
class QueryUtil
  def find_user_info (usernames, strategy)
    @db = Mysql.real_connect ("localhost", "root", "123456", "test", 3306);
    sql = "select * from user_info where"
    if strategy == 1
      usernames.each do | user |
        sql << "username = '"
        sql << user
        sql << "'or"
      end
      sql = sql [0 ..-"or" .length]
    elsif strategy == 2
      need_or = false
      usernames.each do | user |
        sql << "or" if need_or
        sql << "username = '"
        sql << user
        sql << "'"
        need_or = true
      end
    end
    puts sql
    result = @ db.query (sql);
    result.each_hash do | row |
      #Process the data read from the database
    end
    #The data read should be assembled into an object and returned, and omitted here
  ensure
    @ db.close
  end
end
As you can see, your leader teaches your assembly algorithm, using a Boolean variable to control whether you need to add the or keyword. When the for loop is executed for the first time, the boolean value is false, so or is not added. Set the boolean value to true at the end of the loop, so that each time after the loop will add an or keyword to the head, because the method of adding or to the head is used, you don't have to worry about an extra or at the end of the SQL statement Come. Then, in order to keep both algorithms, you add a parameter to the find_user_info method. A strategy value of 1 indicates that the first algorithm is used, and a strategy value of 2 indicates that the second algorithm is used.
In this way, the test code needs to be changed as follows:

qUtil = QueryUtil.new
qUtil.find_user_info (["Tom", "Jim", "Anna"], 2)
Here you specify the parameters to use the second algorithm to assemble SQL statements, and the printed results are exactly the same as using the first algorithm.
You immediately dragged your leader out of your busy schedule and asked him to check your current achievements, but he was still picky as always.
"If you write like this, the logic of the find_user_info method is too complicated, it is not conducive to reading, and it is not conducive to future expansion. If I have the third and fourth algorithms to add, can this method still be seen?" Your leader instructs you. When you encounter this situation, you need to use the strategy mode to solve it. The core idea of the strategy mode is to extract the algorithm and put it into a separate object.
In order to point you, he ignores his busy schedule and starts to teach you how to use strategy mode for optimization.
First define a parent class. The parent class contains a get_sql method. This method simply throws an exception:

class Strategy
  def get_sql usernames
    raise "You should override this method in subclass."
  end
end
Then define both subclasses to inherit the above parent class, and add two algorithms for assembling SQL statements to the two subclasses:

class Strategy1
  def get_sql usernames
    sql = "select * from user_info where"
    usernames.each do | user |
      sql << "username = '"
      sql << user
      sql << "'or"
    end
    sql = sql [0 ..-"or" .length]
  end
end

class Strategy2
  def get_sql usernames
    sql = "select * from user_info where"
    need_or = false
    usernames.each do | user |
      sql << "or" if need_or
      sql << "username = '"
      sql << user
      sql << "'"
      need_or = true
    end
  end
end

Then call the get_sql method of Strategy in the find_user_info method of QueryUtil to get the assembled SQL statement. The code is as follows:

require 'mysql'
 
class QueryUtil
  def find_user_info (usernames, strategy)
    @db = Mysql.real_connect ("localhost", "root", "123456", "test", 3306);
    sql = strategy.get_sql (usernames)
    puts sql
    result = @ db.query (sql);
    result.each_hash do | row |
      #Process the data read from the database
    end
    #The data read should be assembled into an object and returned, and omitted here
  ensure
    @ db.close
  end
end
Finally, when the test code calls the find_user_info method, it only needs to explicitly indicate which policy object needs to be used:

qUtil = QueryUtil.new
qUtil.find_user_info (["Tom", "Jim", "Anna"], Strategy1.new)
qUtil.find_user_info (["Jac", "Joe", "Rose"], Strategy2.new)
The printed SQL statement was not unexpected, as shown below:

select * from user_info where username = 'Tom' or username = 'Jim' or username = 'Anna'
select * from user_info where username = 'Jac' or username = 'Joe' or username = 'Rose'
After modifying the strategy mode, the readability and extensibility of the code have been greatly improved. Even if you need to add new algorithms in the future, you are here!

Strategy patterns and simple factories
Examples of Pattern Combination

demand:

The mall cashier software calculates the expenses based on the unit price and quantity of the items purchased by the customer, and there will be promotional activities, discounts of 20%, discounts of one hundred or three.

1. Use factory mode

#-*-encoding: utf-8-*-

#Cash Charge Abstract Class
class CashSuper
  def accept_cash (money)
  end
end

#Normal charge subclass
class CashNormal <CashSuper
  def accept_cash (money)
    money
  end
end

#Discount toll subcategory
class CashRebate <CashSuper
  attr_accessor: mony_rebate
  
  def initialize (mony_rebate)
    @mony_rebate = mony_rebate
  end

  def accept_cash (money)
    money * mony_rebate
  end
end

#Rebate Fee Sub-Category
class CashReturn <CashSuper
  attr_accessor: mony_condition,: mony_return
  
  def initialize (mony_condition, mony_return)
    @mony_condition = mony_condition
    @mony_return = mony_return
  end

  def accept_cash (money)
    if money> mony_condition
      money-(money / mony_condition) * mony_return
    end
  end
end

#Cash toll factory
class CashFactory
  def self.create_cash_accept (type)
    case type
    when 'normal charge'
      CashNormal.new ()
    when '20% off '
      CashRebate.new (0.8)
    when 'full three hundred minus 100'
      CashReturn.new (300,100)
    end
  end
end

cash0 = CashFactory.create_cash_accept ('Normal charge')
p cash0.accept_cash (700)

cash1 = CashFactory.create_cash_accept ('20% off ')
p cash1.accept_cash (700)

cash2 = CashFactory.create_cash_accept ('Three hundred minus 100')
p cash2.accept_cash (700)

Achieved custom discount ratios and full deductions.

Problems:

When increasing the types of activities, 50% off, full 500 minus 200, you need to add a branch structure in the factory class.

There are a variety of activities. It is also possible to increase the point activity. If you add 100 points and 10 points, you can receive the event prizes. At this time, you need to add a sub-category.

But every time you increase the activity, you have to modify the factory class, which is a very bad way to deal with it. When there are changes in the algorithm, there should be a better way.

2. Strategy mode

CashSuper and subclasses are unchanged, adding the following:

class CashContext
  
  attr_accessor: cs
  
  def initialize (c_super)
    @cs = c_super
  end
  
  def result (money)
    cs.accept_cash (money)
  end

end

type = '20% off '
cs = case type
  when 'normal charge'
    CashContext.new (CashNormal.new ())
  when '20% off '
    CashContext.new (CashRebate.new (0.8))
  when 'full three hundred minus 100'
    CashContext.new (CashReturn.new (300,100))
  end
p cs.result (700)

The CashContext class encapsulates different CashSuper subclasses and returns the corresponding result. That is, different algorithms are encapsulated, no matter how the algorithm changes. You can use result to get the result.
However, there is currently a problem. Users need to make a judgment to choose which algorithm to use. Can be combined with simple workshop classes.

3. Combination of strategy and simple workshop

class CashContext
  
  attr_accessor: cs
  
  def initialize (type)
    case type
    when 'normal charge'
      @cs = CashNormal.new ()
    when '20% off '
      @cs = CashRebate.new (0.8)
    when 'full three hundred minus 100'
      @cs = CashReturn.new (300,100)
    end
  end
  
  def result (money)
    cs.accept_cash (money)
  end

end

cs = CashContext.new ('20% off ')

p cs.result (700)

CashContext instantiates different subclasses. (Simple factory)
The sub-class selection process is transferred internally, and the algorithm (strategy mode) is encapsulated.

It is simpler for the caller to pass in the parameters (activity type, original price) and get the final result.
Here, the user only needs to know one class (CashContext), and the simple factory needs to know two classes (the Accept_cash method of CashFactory and CashFactory), which means that the encapsulation is more thorough.

Articles you may be interested in:
Explain in detail the structure of composition patterns and their use in Ruby design pattern programming
Two application examples of the template method pattern in the design pattern in Ruby
Example to explain how Ruby uses the decorator pattern in design patterns
Examples of using the builder pattern in Ruby design pattern programming
Explain the use of singleton pattern in Ruby design pattern programming
Ruby design mode programming adapter mode combat strategy
Ruby code examples using proxy and decorative patterns in design patterns
Simple factory pattern and factory method pattern in Ruby using design patterns
Analysis of application examples of appearance mode in Ruby design pattern programming

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.