首頁 >web前端 >js教程 >詳解JavaScript的策略模式程式設計_基礎知識

詳解JavaScript的策略模式程式設計_基礎知識

WBOY
WBOY原創
2016-05-16 15:53:131017瀏覽

 我喜歡策略設計模式。我盡可能多的試著去使用它。究其本質,策略模式使用委託去解耦使用它們的演算法類別。

這樣做有幾個好處。他可以防止使用大條件語句來決定哪些演算法用於特定類型的物件。將關注點分離開來,因此降低了客戶端的複雜度,同時還可以促進子類化的組成。它提高了模組化和可測性。每一個演算法都可以單獨測試。每一個客戶端都可以模擬演算法。任意的客戶端都能使用任何演算法。他們可以互調。就像樂高積木一樣。

為了實現策略模式,通常有兩位參與者:

該策略的對象,封裝了演算法。

客戶端(上下文)對象,以即插即用的方式能使用任何策略。

這裡介紹了我在Javascrip裡,怎樣使用策略模式,在混亂無序的環境中如何使用它將庫拆成小插件,以及即插即用包的。

函數作為策略

一個函數提供了一種封裝演算法的絕佳方式,同時可以作為一種策略來使用。只需通過一個到客戶端的函數並確保你的客戶端能呼叫該策略。

我們用一個例子來證明。假設我們想建立一個Greeter 類別。它要做的就是和人打招呼。我們希望Greeter 類別能知道跟人打招呼的不同方式。為了實現這個想法,我們為打招呼創建不同的策略。
 

// Greeter is a class of object that can greet people.
// It can learn different ways of greeting people through
// 'Strategies.'
//
// This is the Greeter constructor.
var Greeter = function(strategy) {
this.strategy = strategy;
};
 
// Greeter provides a greet function that is going to
// greet people using the Strategy passed to the constructor.
Greeter.prototype.greet = function() {
return this.strategy();
};
 
// Since a function encapsulates an algorithm, it makes a perfect
// candidate for a Strategy.
//
// Here are a couple of Strategies to use with our Greeter.
var politeGreetingStrategy = function() {
console.log("Hello.");
};
 
var friendlyGreetingStrategy = function() {
console.log("Hey!");
};
 
var boredGreetingStrategy = function() {
console.log("sup.");
};
 
// Let's use these strategies!
var politeGreeter = new Greeter(politeGreetingStrategy);
var friendlyGreeter = new Greeter(friendlyGreetingStrategy);
var boredGreeter = new Greeter(boredGreetingStrategy);
 
console.log(politeGreeter.greet()); //=> Hello.
console.log(friendlyGreeter.greet()); //=> Hey!
console.log(boredGreeter.greet()); //=> sup.

在上面的例子中,Greeter 是客戶端,並有三種策略。正如你所看到的,Greeter 知道如何使用演算法,但對於演算法的細節卻一無所知。

對於複雜的演算法,一個簡單的函數往往不能滿足。在這種情況下,對好的方式就是按照物件來定義。
 
類別作為策略

策略同樣可以是類,特別是當算比上述例子中使用的人為的(策略/演算法)更複雜的時候。使用類別的話,允許你為每一種策略定義一個介面。

在下面的例子中,證實了這一點。
 

// We can also leverage the power of Prototypes in Javascript to create
// classes that act as strategies.
//
// Here, we create an abstract class that will serve as the interface
// for all our strategies. It isn't needed, but it's good for documenting
// purposes.
var Strategy = function() {};
 
Strategy.prototype.execute = function() {
 throw new Error('Strategy#execute needs to be overridden.')
};
 
// Like above, we want to create Greeting strategies. Let's subclass
// our Strategy class to define them. Notice that the parent class
// requires its children to override the execute method.
var GreetingStrategy = function() {};
GreetingStrategy.prototype = Object.create(Strategy.prototype);
 
// Here is the `execute` method, which is part of the public interface of
// our Strategy-based objects. Notice how I implemented this method in term of
// of other methods. This pattern is called a Template Method, and you'll see
// the benefits later on.
GreetingStrategy.prototype.execute = function() {
 return this.sayHi() + this.sayBye();
};
 
GreetingStrategy.prototype.sayHi = function() {
 return "Hello, ";
};
 
GreetingStrategy.prototype.sayBye = function() {
 return "Goodbye.";
};
 
// We can already try out our Strategy. It requires a little tweak in the
// Greeter class before, though.
Greeter.prototype.greet = function() {
 return this.strategy.execute();
};
 
var greeter = new Greeter(new GreetingStrategy());
greeter.greet() //=> 'Hello, Goodbye.'

透過使用類,我們與anexecutemethod物件定義了一個策略。客戶端可以使用任何策略實作該介面。

也注意到我又是怎樣創建GreetingStrategy的。有趣的部分是對methodexecute的重載。它以其他函數的形式定義。現在類別的後繼子類別可以改變特定的行為,如thesayHiorsayByemethod,並不會改變常規的演算法。這種模式叫做模板方法,非常適合策略模式。

讓我們看個究竟。
 

// Since the GreetingStrategy#execute method uses methods to define its algorithm,
// the Template Method pattern, we can subclass it and simply override one of those
// methods to alter the behavior without changing the algorithm.
 
var PoliteGreetingStrategy = function() {};
PoliteGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype);
PoliteGreetingStrategy.prototype.sayHi = function() {
 return "Welcome sir, ";
};
 
var FriendlyGreetingStrategy = function() {};
FriendlyGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype);
FriendlyGreetingStrategy.prototype.sayHi = function() {
 return "Hey, ";
};
 
var BoredGreetingStrategy = function() {};
BoredGreetingStrategy.prototype = Object.create(GreetingStrategy.prototype);
BoredGreetingStrategy.prototype.sayHi = function() {
 return "sup, ";
};
 
var politeGreeter  = new Greeter(new PoliteGreetingStrategy());
var friendlyGreeter = new Greeter(new FriendlyGreetingStrategy());
var boredGreeter  = new Greeter(new BoredGreetingStrategy());
 
politeGreeter.greet();  //=> 'Welcome sir, Goodbye.'
friendlyGreeter.greet(); //=> 'Hey, Goodbye.'
boredGreeter.greet();  //=> 'sup, Goodbye.'

GreetingStrategy 透過指定theexecutemethod的步驟,創建了一個類別的演算法。在上面的程式碼片段中,我們透過創建專門的演算法從而利用了這一點。

沒有使用子類,我們的Greeter 依然展現出一種多態行為。沒有必要在Greeter 的不同類型上進行切換來觸發正確的演算法。這一切都綁定到每一個Greeter 物件上。
 

var greeters = [
 new Greeter(new BoredGreetingStrategy()),
 new Greeter(new PoliteGreetingStrategy()),
 new Greeter(new FriendlyGreetingStrategy()),
];
 
greeters.forEach(function(greeter) {
  
 // Since each greeter knows its strategy, there's no need
 // to do any type checking. We just greet, and the object
 // knows how to handle it.
 greeter.greet();
});

多環境下的策略模式

我最喜歡的有關策略模式的例子之一,實在 Passport.js庫中。 Passport.js提供了在Node中處理身份驗證的簡單方式。大範圍內的供應商都支援(Facebook, Twitter, Google等等),每一個都作為一種策略實現。

該函式庫作為一個npm套件是可行的,其所有的策略也一樣。庫的使用者可以決定為他們特有的用例安裝哪一個npm套件。以下是展示其如何實現的程式碼片段:
 

// Taken from http://passportjs.org
 
var passport = require('passport')
   
  // Each authentication mechanism is provided as an npm package.
  // These packages expose a Strategy object.
 , LocalStrategy = require('passport-local').Strategy
 , FacebookStrategy = require('passport-facebook').Strategy;
 
// Passport can be instanciated using any Strategy.
passport.use(new LocalStrategy(
 function(username, password, done) {
  User.findOne({ username: username }, function (err, user) {
   if (err) { return done(err); }
   if (!user) {
    return done(null, false, { message: 'Incorrect username.' });
   }
   if (!user.validPassword(password)) {
    return done(null, false, { message: 'Incorrect password.' });
   }
   return done(null, user);
  });
 }
));
 
// In this case, we instanciate a Facebook Strategy
passport.use(new FacebookStrategy({
  clientID: FACEBOOK_APP_ID,
  clientSecret: FACEBOOK_APP_SECRET,
  callbackURL: "http://www.example.com/auth/facebook/callback"
 },
 function(accessToken, refreshToken, profile, done) {
  User.findOrCreate(..., function(err, user) {
   if (err) { return done(err); }
   done(null, user);
  });
 }
));

Passport.js庫只配備了一兩個簡單的身份驗證機制。除此之外,它沒有超過一個符合上下文物件的一個策略類別的介面。這種機制讓他的使用者,很容易的實現他們自己的身份驗證機制,而對專案不產生不利的影響。
 
反思

策略模式為你的程式碼提供了一種增加模組化和可測性的方式。這並不意味著(策略模式)總是有效。 Mixins 同樣可以用來進行功能性注入,如在運行時的一個物件的演算法。扁平的老式 duck-typing多型有時也可以夠簡單。

然而,使用策略模式允許你在一開始沒有引入大型體系的情況下,隨著負載型的增長,擴大你的程式碼的規模。正如我們在Passport.js範例中看到的一樣,對於維護人員在將來增加另外的策略,將變得更加方便。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn