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