Ruby design mode Programming Adapter mode combat Strategy _ruby topics

Source: Internet
Author: User
Tags datetime hash json postgresql sprintf

Adapter mode
adapter patterns can be used to wrap different interfaces and to provide a unified interface, or to make an object look like an object of another type. In a static type of programming language, we often use it to satisfy the characteristics of a type system, but in a weakly typed programming language like Ruby, we don't need to do that. However, it still has a lot of meaning for us.
When using a third party class or library, we often start with this example (start out fine):

def find_nearest_restaurant (Locator)
 Locator.nearest (: Restaurant, Self.lat, Self.lon)
end

Let's assume there's an interface for locator, but what if we want find_nearest_restaurant to be able to support another library? At this point we may be trying to add new special scene Processing:

def find_nearest_restaurant (Locator)
 if locator.is_a? Geofish
  Locator.nearest (: Restaurant, Self.lat, Self.lon)
 elsif locator.is_a? Actsasfound
  locator.find_food (: Lat => self.lat,: Lon => Self.lon)
 else
  raise Notimplementederror , "#{locator.class.name} is not supported."
 End End

This is a more practical solution. Perhaps we no longer need to think about supporting another library. Or maybe find_nearest_restaurant is the only scene where we use locator.
So what if you really need to support a new locator? That is that you have three specific scenes. And what if you need to implement the Find_nearest_hospital method? This way you need to take care of two different places while maintaining these three specific scenarios. When you think this solution is no longer feasible, you need to consider the adapter mode.
In this example, we can write adapters for geofish and Actsasfound, so that in our other code we don't need to know which library we're currently using:

def find_nearest_hospital (Locator)
 Locator.find:type =>: Hospital,
        : Lat => self.lat,
        : Lon => Self.lon
End

locator = Geofishadapter.new (geo_fish_locator)
find_nearest_hospital (Locator)

So let's take a look at the real code for the hypothetical example.

Instance
early in the morning, your leader rushed over to find you: "Quick, quick, urgent task!" Recently ChinaJoy is about to start, and the boss asks for an intuitive way to see the number of people on the line in each of our new online games. ”
You looked at the date, didn't you! This where is to start immediately, clearly is already started! How could it be too late?
"It's okay. "Your leader Comfort you:" The function is really very simple, the interface has been provided, you just need to call the line. ”
Well, you are reluctant to accept that you are accustomed to this sudden new demand.
Your leader to give you a specific description of the demand, your game currently has three, a dress has been open for some time, two and three clothes are new to open clothes. The design of the interface is very lightweight, you only need to call Utility.online_player_count (Fixnum), the corresponding value of each suit can be used to get the corresponding number of online players, such as the introduction of 1, two to the incoming 2, the three are introduced into 3. If you pass in a nonexistent suit, you will return-1. Then you just have to assemble the data into XML, and the specific display function is done by your leader.
Well, it doesn't sound very complicated, so if you start starting now it seems like there's still time, so you hit the code right away.
First, define a parent class Playercount for counting the number of online people, the following code:

Class Playercount 
 
  def server_name 
    raise "You should override this method in subclass." 
  End 
   
  def player_count 
    raise "Your should override this method in subclass." 
  End End 
 
 

Then define three statistical classes to inherit Playercount, corresponding to three different suits, as follows:

Class ServerOne < Playercount 
 
  def server_name 
    "one-suit" end 
   
  def player_count 
    Utility.online_ Player_count (1) 
  end 

class Servertwo < Playercount 
 
  def server_name 
    "Two suits" 
   
  end def player_count 
    Utility.online_player_count (2) 
  end 

class Serverthree < Playercount 
 
  def server_name 
    "three suits" 
  end 
   
  def player_count 
    Utility.online_player_count (3) 
  end End 
 
 

Then you define a Xmlbuilder class that encapsulates the data in each service into an XML format, as follows:

Class Xmlbuilder 
 
  def self.build_xml player 
    builder = "" 
    builder << "<root>" 
    Builder << "<server>" << player.server_name << "</server>" 
    builder << "<player_ Count> "<< player.player_count.to_s <<" </player_count> " 
    builder <<" </root> " End End 
 
 

In this case, all the code will be completed, if you want to check the number of online players only need to call:

Xmlbuilder.build_xml (serverone.new) 

To view the number of online players, you only need to call:

Xmlbuilder.build_xml (servertwo.new) 

To view the number of online players, just call:

Xmlbuilder.build_xml (serverthree.new) 

Hey? You find that when you look at the number of online players, the return value is always 1, and it's normal to look at the second and third suits.
You have to call your leader: "I feel that I wrote the code is not a problem, but the query a number of online players always return-1, why this?" ”
Oh "Your leader suddenly thought," This is my question, the front did not explain to you clearly. Since one of our suits has been open for some time, the ability to query the number of online players has long been available, using the Serverfirst class. At that time to write Utility.online_player_count () This method is mainly for the new open two and three uniforms, did not put a suit of query function to repeat again. In this case, you can use the adapter pattern, which is the solution to the problem of incompatibility between interfaces. ”
In fact, the use of adapter mode is very simple, the core idea is that as long as two incompatible interfaces can be normal docking on the line. In the code above, Playercount is used to assemble XML in Xmlbuilder, and Serverfirst does not inherit Playercount, This time you need an adapter class to build a bridge between Xmlbuilder and Serverfirst, and ServerOne will undoubtedly act as the adapter class role. Modify the ServerOne code as follows:

Class ServerOne < Playercount 
 
  def initialize 
    @serverFirst = serverfirst.new 
  end 
 
  def server_name 
    "one suit" 
  end 
   
  def player_count 
    @serverFirst. Online_player_count 
  End 
 


This through ServerOne adaptation, Xmlbuilder and Serverfirst between the successful completion of docking! When we use it, we don't even need to know that there is a Serverfirst class, just the normal creation of the ServerOne instance.
It should be noted that the adapter pattern is not the type that makes the architecture more reasonable, but more often it acts as a fireman and helps solve the problem of interface mismatch caused by the unreasonable design of the previous architecture. A better approach is to try to think about what might happen later in the design, and don't learn from your leader on this issue.

Multijson
Activesupport in JSON-formatted decoding, the Multijson is used, which is an adapter for the JSON library. Each library can parse JSON, but the practice is different. Let's look at the adapters for OJ and YAJL, respectively. (Hint: You can enter QW Multi_json in the command line to view the source code.) )

Module Multijson
 module Adapters
  class Oj < Adapter
   #
   ... def load (string, options={})
    Options[:symbol_keys] = Options.delete (: Symbolize_keys)
    :: Oj.load (String, Options)
   End
   # ...

The OJ adapter modifies the options hash table and uses Hash#delete to convert: Symbolize_keys to OJ: Symbol_keys entry:

Options = {: Symbolize_keys => true}
Options[:symbol_keys] = Options.delete (: Symbolize_keys) # => True
Options                         # => {: Symbol_keys=>true}

Next Multijson Calls:: Oj.load (String, Options). The Multijson API is very similar to OJ's original API, and there is no need to repeat it here. But have you noticed how OJ is quoted? :: Oj refers to the Oj class at the top level, not the multijson::adapters::oj.
Now let's see how Multijson fit into the YAJL library:

Module Multijson
 module Adapters
  class Yajl < Adapter
   #
   ... def load (string, options={})
    :: Yajl::P arser.new (: Symbolize_keys => Options[:symbolize_keys]). Parse (String)
   End
   # ...

This adapter implements the Load method in different ways. The way to YAJL is to create the strength of a parser, and then call the Passed-in strings string as a parameter Yajl::P Arser#parse method. The processing on the options hash table is also slightly different. Only: The Symbolize_keys item is passed to the YAJL.
These JSON adapters seem trivial, but they allow you to switch between different libraries at will, without having to update the code in every location that parses JSON.
ActiveRecord
Many JSON libraries tend to follow similar patterns, which makes adaptation work quite easy. But what happens if you're dealing with more complicated situations? The ActiveRecord contains adapters for different databases. Although PostgreSQL and MySQL are SQL databases, there are a lot of differences between them, and activerecord masks these differences by using the adapter pattern. (Hint: Enter QW activerecord the command line to view ActiveRecord code)
Open the Lib/connection_adapters directory in the ActiveRecord code base, which will have adapters for Postgresql,mysql and SQLite. In addition, there is an adapter named Abstractadapter, which serves as the base class for each specific adapter. Abstractadapter implements features that are common in most databases, which are redefined in subclasses such as Postgresqladapter and Abstractmysqladapter, And Abstractmysqladapter is the other two different MySQL adapter--mysqladapter and mysql2adapter--'s parent class. Let's take a look at some real-world examples to see how they work together.
PostgreSQL and MySQL are slightly different implementations of the SQL dialect. The query statement SELECT * from users can execute correctly in all two databases, but they may be slightly different on some types of processing. In MySQL and PostgreSQL, the time format is different. Where PostgreSQL supports microsecond-level time, MySQL is only supported in the latest release of a stable release. So how do these two adapters deal with this difference?
ActiveRecord the Quoted_date reference date by being mixed into Abstractadapter's activerecord::connectionadapters::quoting. The implementation in Abstractadapter simply formats the date:

def quoted_date (value)
 #
 ... value.to_s (:d b) End

Activesupport in Rails expands the time#to_s to receive a symbolic type parameter that represents a format name. The format represented by:d B is%y-%m-%d%h:%m:%s:

# Examples of common formats:
Time.now.to_s (:d b)   #=> "2014-02-19 06:08:13"
Time.now.to_s (: short)  #=> "Feb 06:08"
Time.now.to_s (: rfc822) #=> "Wed, Feb 2014 06:08:13 +0000"

MySQL adapters do not rewrite the Quoted_date method, they will naturally inherit this behavior. On the other side, Postgresqladapter made two modifications to date processing:

def quoted_date (value) result
 = Super
 if Value.acts_like. (: Time) && value.respond_to? (: USEC)
  result = "#{result}.#{sprintf ("%06d ", Value.usec)}"
 End

 if value.year < 0 result
  = result.sub (/^-/, "") + "BC" end result end


It calls the Super method at the very beginning, so it will also get a date similar to the one formatted in MySQL. Next, it detects whether value is like a specific time. This is an extended method in Activesupport that returns True when an object resembles an instance of a time type. This makes it easier to show that various objects have been assumed to be objects similar to time. (Hint: Interested in the Acts_like method?) Please perform QW activesupport at the command line and read core_ext/object/acts_like.rb)
The second part of the condition checks whether value has a usec method for returning milliseconds. If the number of milliseconds can be obtained, it is appended to the end of the result string through the sprintf method. Like many time formats, sprintf has a number of different ways to format numbers:

sprintf ("%06d",) #=> "000032" sprintf
("%6d",) #=> "sprintf"
("%d",  ) #=> "32" c16/>sprintf ("%.2f",) #=> "32.00"

Finally, if the date is a negative number, Postgresqladapter will reformat the date by adding "BC", which is the actual requirement of the PostgreSQL database:

SELECT ' 2000-01-20 ':: timestamp;
--2000-01-20 00:00:00
SELECT ' 2000-01-20 BC ':: timestamp;
--2000-01-20 00:00:00 BC
SELECT ' -2000-01-20 ':: timestamp;
--Error:time Zone displacement out of range: "-2000-01-20"

This is just a tiny way to ActiveRecord multiple APIs, but it can help you get rid of the differences and annoyances caused by the details of different databases.
Another point that reflects the different points of the SQL database is how the database tables are created. The processing of primary keys in MySQL and in PostgreSQL is different:

# abstractmysqladapter
native_database_types = {
 :p rimary_key => ' int (one) DEFAULT NULL auto_increment PRIMARY KEY ",
 # ...
}

# postgresqladapter
native_database_types = {
 Primary_key: "Serial primary Key",
 # ...
}

Both adapters can understand how primary keys are represented in ActiveRecord, but they will translate this into a different SQL statement when creating a new table. The next time you're writing a migration or executing a query, think about the ActiveRecord adapters and all the tiny things they do for you.
datetime and Time
when Multijson and ActiveRecord implement traditional adapters, Ruby's flexibility makes another solution possible. Both datetime and time are used to represent times, but they are different in their internal processing. Despite these nuances, the APIs they expose are extremely similar (hint: Execute QW activesupport in the command line to see the relevant code here):

t = Time.now
t.day   #=> (Day of     month)
t.wday #=>  3 (Day of     week)
t.usec  #= > 371552   (microseconds)
t.to_i  #=> 1392871392 (Epoch secconds)

d = DateTime.Now
d.day   #=>     (Day of month)
D.wday  #=> 3     (day of week)
d.usec  #=> nomethoderror:undefined method ' usec '
d.to_i  #=> nomethoderror:undefined method ' To_i '

Activesupport by adding missing methods to directly modify datetime and time, and then erase the difference between the two. From an instance, here's an example that shows how Activesupport defines datetime#to_i:

Class DateTime
 def to_i
  seconds_since_unix_epoch.to_i
 end

 def Seconds_since_unix_epoch
  (JD- 2440588) * 86400-offset_in_seconds + seconds_since_midnight
 end

 def offset_in_seconds
  (offset * 86400). To _i
 End

 def seconds_since_midnight
  sec + (min *) + (hour * 3600)
End

Each of the methods used for support, Seconds_since_unix_epoch,offset_in_seconds, and Seconds_since_ Midnight uses or expands the existing APIs in DateTime to define a method that matches the time.
If the adapter we saw earlier is relative to the external adapter of the adaptive object, then what we see now can be called an internal adapter. Unlike an external adapter, this approach is limited to existing APIs and may cause some troublesome contradictions. For example, datetime and time may behave differently in special scenarios:

datetime = = Time #=> true
datetime + 1   #=> 2014-02-26 07:32:39 time
+ 1     #=> 2014-02-25 07:32: 40

When you add 1, DateTime adds a day, and time adds a second. When you need to use them, remember that activesupport, based on these differences, provides methods or classes that ensure consistent behavior, such as change and duration.
Is this a good model? It's certainly convenient, but as you've seen, you still need to be aware of some of the differences.
Summary
Design patterns are not only required by Java. Rails uses design patterns to provide a unified interface for JSON parsing and database maintenance. Because of Ruby's flexibility, classes like datetime and time can be directly modified to provide similar interfaces. The rails source code is a paradise that allows you to tap into the real world of different design patterns.
In this practice, we have also uncovered some interesting code:

    • Hash[:foo] = Hash.delete (: bar) is a clever way to rename an item in a hash table.
    • Call:: ClassName will call the top-level class.
    • Activesupport adds an optional parameter format representing the format to time, date, and other classes.
    • sprintf can be used to format numbers.

Want to explore more knowledge? Go back and see how Multijson handles and parses the format. READ carefully the code for the ActiveRecord adapter you use in your database. Browse the Xmlmini for the XML adapter in the Activesupport, which is similar to the JSON adapter in Multijson. In these there will be a lot to learn.

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.