Instance parsing the use of strategy strategy patterns in Ruby design pattern programming _ruby Special Topics

Source: Internet
Author: User


Today your leader to find you, hope you can help him a little busy, he is now anxious to go to the meeting. What can I do for you? You're curious.
He said to you, the current database of your project has a user information table, which contains a very user data, now need to complete a selective query user information function. He said he'd pass it on to you. An array that contains a lot of user names, and you need to check out their corresponding data according to the username.
This function is very simple, you readily agreed. Since your project is using a MySQL database, you quickly write 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 (S QL); 
    Result.each_hash do |row| 
      #Processing data read from the database
     End
     #The data should be assembled into an object and returned later
, omit the 
  ensure 
    @db. 
 


Here, the SQL statements are assembled according to the incoming user array group, and then the corresponding rows are found in the database. For the sake of debugging, you will also print out the assembled SQL statements.
Then, you write 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 has gone wrong. So you immediately checked the printed SQL statement, and found the problem.



SELECT * from user_info where username = ' Tom ' or username = ' Jim ' or username = ' Anna ' or
The assembled SQL statements add an OR keyword at the end! Because the For loop does not have to add or to the last piece of data, the code is stupid enough to add an or keyword to the last piece of data, causing the SQL statement syntax to go wrong.
What can we do about it?
There it is! You have a flash of inspiration and come up with a solution. After the SQL statements have been assembled, it is better to intercept the last or previous position. So you change the code to look like the following:


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| 
      #Processing data read from the database
     End
     #The data should be assembled into an object and returned later
, omit the 
  ensure 
    @db. 
 


Using the Intercept substring method of string, only to the part before the last or, running the test code again, everything is fine, and the printed SQL statement looks like this:


SELECT * from user_info where username = ' Tom ' or username = ' Jim ' or username = ' Anna ' 


All right, finish it! You are full of confidence.
After the meeting of your leader, come and see what you have done. Overall, he was quite satisfied, but for the SQL statement you used to assemble the algorithm, he always felt something was wrong, but he couldn't say where it was bad. So he tells you another algorithm for assembling SQL statements, which allows you to add to the code, but don't delete the previous algorithm, keep it, and then run away. So, you add the algorithm that he just taught you, and the code looks like this:


 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-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, omit the ensure @db. Close End 


You can see that you leader to teach you the assembly algorithm, using a Boolean variable to control whether you need to add an or this keyword, the first time the for loop execution because the Boolean value is False, so will not add or, at the end of the loop to assign the Boolean value to True, After that, the loop will add an or keyword to the head each time, and you don't have to worry about adding an or to the end of the SQL statement because you used the method of using the head to add or. And then you save two algorithms, add a parameter to the Find_user_info method, and a strategy value of 1 means the first algorithm, and the strategy value of 2 to use the second algorithm.
This test code also needs to be changed to the following ways:


Qutil = queryutil.new 
qutil.find_user_info (["Tom", "Jim", "Anna"], 2) 


Here you indicate by parameter that the second algorithm is used to assemble the SQL statement, and that the printed result is exactly the same as using the first algorithm.
You immediately dragged your leader from your busy schedule, let him test your current results, but he is still as picky as ever.
"As you write this, the logic of the find_user_info is too complicated to read, and not conducive to future expansion, if I have a third fourth algorithm to add, this method can still see?" "Your leader to guide you, in this case, we need to use the strategy model to solve, the core idea of the strategy model is to extract the algorithm into a separate object."
In order to guide you, he ignored his busy schedule, began to teach you how to use the Strategy model for optimization.
First you define a parent class, and the parent class contains a Get_sql method that simply throws an exception:


Class Strategy 
  def get_sql usernames 
    raise "you should override this method in subclass." 
  End End 
 


Then the definition of two subclasses inherits the above parent class and adds the two algorithms for assembling SQL statements to two subclasses respectively:


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 

class Strategy2 
  def get_sql usernames 
    sql = ' select * from User_info wher E " 
    need_or = False 
    Usernames.each do |user| 
      SQL << "or" If Need_or 
      sql << "username = '" 
      SQL << user 
      sql << "'" 
      need_or = True End End 
 


You can then call the strategy Get_sql method in the Queryutil Find_user_info method to get the assembled SQL statement, as shown in the following code:


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| 
      #Processing data read from the database
     End
     #The data should be assembled into an object and returned later
, omit the 
  ensure 
    @db. 
 


Finally, when the test code calls the Find_user_info method, it only needs to indicate which policy object you want to use:


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 is not expected at all, as follows:


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 using the policy mode modification, the code readability and extensibility have greatly improved, even if you need to add a new algorithm, you are extremely easy!



Example of a combination of a policy model and a simple factory model



Demand:



Shopping center cashier software, according to the customer purchase items of unit price and quantity, calculate the cost, there will be promotional activities, 80 percent dozen, 300 minus 100 and so on.



1. Use Factory mode


#-*-Encoding:utf-8-*-#Cash charge abstract class class Cashsuper def accept_cash (money) End #Normal charge subclass class Cashnormal <Cas
  
  Hsuper def accept_cash (money), End #Discounted charge subclass class Cashrebate <Cashsuper attr_accessor: mony_rebate
  def initialize (mony_rebate) @mony_rebate = Mony_rebate End def accept_cash (money) money * mony_rebate End #rebate charge subclass class Cashreturn <Cashsuper attr_accessor: mony_condition: Mony_return def initialize (mony_co
    Ndition, Mony_return) @mony_condition = mony_condition @mony_return = Mony_return End def accept_cash (money) If Money> Mony_condition- (money / mony_condition) * Mony_return end end #cash fee factory class class Cash Factory def self.create_cash_accept (type) case type when 'normal charge' cashnormal.new () when 'call 80 percent' cash Rebate.new (0.8) When 'full 300 reduction' cashreturn.new (300,100) end end cash0 = cashfactory.create_cash_accept ('normal charge') p Cash0. accept_Cash (m) cash1 = Cashfactory.create_cash_accept ('Play 80 percent') p Cash1.accept_cash (m) cash2 = Cashfactory.create_cash_  Accept (' full 300 reduction ') p Cash2.accept_cash (700)


The custom discount ratio and the number of full cuts are achieved.



The problems that exist:



When you increase the type of activity, call 50 percent, 500 minus 200, and you need to add a branching structure to the factory class.



The activity is various, also may increase the integral activity, full 100 plus 10 integral, the integral may collect the activity prize, then must add one subclass.



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



2. Strategy model



The Cashsuper and subclasses are invariant, adding the following:


Class Cashcontext
  
  Attr_accessor:cs
  
  def initialize (c_super)
    @cs = C_super
  end
  
  def result (money)
    Cs.accept_cash End

type = ' Call 80 percent '
cs=case type when
  ' normal charge '
    Cashcontext.new (Cashnormal.new ()) when
  ' call 80 percent '
    cashcontext.new (cashrebate.new (0.8)) when
  ' full 300 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, the different algorithms are encapsulated, no matter how the algorithm changes. Results can be obtained using result.
However, there is a problem, users need to make a decision to choose which algorithm to use. Can be combined with a simple workshop class.



3. Strategy and simple workshop combination


Class Cashcontext
  
  Attr_accessor:cs
  
  def initialize (type) case
    type when
    ' normal charge '
      @cs = Cashnormal.new () When
    ' play 80 percent '
      @cs = cashrebate.new (0.8) When
    ' full 300 reduction '
      @cs = cashreturn.new (300,100) End-def result (money)
  Cs.accept_cash end

cs=cashcontext.new ( ' hit 80 percent ')

P Cs.result (700)


Different subclasses are instantiated in the Cashcontext. (Simple Factory)
The process of selecting subclasses is moved internally to encapsulate the algorithm (policy pattern).



The caller can get the final result by using a simpler, incoming parameter (activity type, original price).
Users here need only know a class (Cashcontext), and a simple workshop needs to know two classes (Cashfactory Accept_cash methods and cashfactory), which means the encapsulation is more thorough.





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.