2010-10-18 30 views
5

Tôi cần tạo mẫu chiến lược trong đó người dùng chọn bốn chiến lược từ danh sách hai mươi hoặc ba mươi đối tượng chiến lược duy nhất. Danh sách các chiến lược sẽ được mở rộng khi dự án đáo hạn và người dùng có thể thay đổi chiến lược đã chọn của họ bất kỳ lúc nào.Mã này có quá giòn không?

Tôi dự định lưu trữ tên chiến lược mà họ đã chọn làm chuỗi và sau đó sử dụng phương pháp như thế này để tải các lớp chiến lược tương ứng với chuỗi họ đã chọn.

class StrategyManager { // simplified for the example 
    public $selectedStrategies = array(); 
    public function __construct($userStrategies) { 
     $this->selectedStrategies = array(
      'first' => new $userStrategies['first'], 
      'second' => new $userStrategies['second'], 
      'third' => new $userStrategies['third'], 
      'fourth' => new $userStrategies['fourth'] 
     ); 
    } 

    public function do_first() { 
     $this->selectedStrategies['first']->execute(); 
    } 

    public function do_second() { 
     $this->selectedStrategies['second']->execute(); 
    } 

    public function do_third() { 
     $this->selectedStrategies['third']->execute(); 
    } 

    public function do_fourth() { 
     $this->selectedStrategies['fourth']->execute(); 
    } 
} 

Tôi đang cố gắng tránh tuyên bố chuyển đổi lớn. Mối quan tâm của tôi là điều này có vẻ như là Stringly Typed. Có cách nào tốt hơn để hoàn thành mục tiêu này mà không sử dụng câu lệnh chuyển đổi có điều kiện hoặc chuyển đổi lớn không?

BTW: Người dùng không nhập chuỗi khi chọn bốn chiến lược. Tôi sẽ cần phải duy trì một danh sách các chuỗi để trình bày cho người dùng trong một hộp chọn và thêm các chuỗi mới vào danh sách khi tôi thêm các đối tượng chiến lược mới.

Giải thích
ircmaxell bày tỏ một chút nhầm lẫn về những gì nó là tôi đang cố gắng để làm. Trong ví dụ trên, người dùng đã chọn bốn chiến lược từ một danh sách và chúng được chuyển tới hàm tạo của StrategyManager dưới dạng một chuỗi các chuỗi. Các đối tượng chiến lược tương ứng được tạo và lưu trữ trong mảng nội bộ, $this->selectedStrategies

"đầu tiên", "thứ hai", "thứ ba" và "thứ tư" là các khóa mảng của mảng nội bộ cho bốn chiến lược được chọn khác nhau. Sau khi đối tượng StrategyManager được xây dựng, ứng dụng sử dụng phương thức execute của bốn chiến lược trong những khoảnh khắc khác nhau trong suốt quá trình của quá trình.

Vì vậy, tóm lại ... mỗi khi ứng dụng cần thực thi phương pháp Chiến lược số "một", và kết quả khác nhau tùy thuộc vào chiến lược nào được người dùng chọn cho Chiến lược "một"

+0

Tôi đang bối rối. Chiến lược "đầu tiên", "thứ hai", "thứ ba" và "thứ tư" có thể khác nhau hoặc chúng là một loạt các lệnh cho chiến lược đã chọn (được chọn trước khi xây dựng trình quản lý). Và nếu có, mô hình [Chain of Responsibility] (http://sourcemaking.com/design_patterns/chain_of_responsibility) hay [Command] (http://sourcemaking.com/design_patterns/command) có hoạt động tốt hơn không? Bạn có thể giải thích chính xác những gì bạn đang cố gắng làm (và những gì mã không, tại sao các chiến lược khác nhau tồn tại)? – ircmaxell

+0

Tôi sẽ cập nhật câu hỏi. – Stephen

+0

Luôn có 4 chiến lược? Và chúng luôn luôn được thực hiện theo thứ tự? Hay họ là bốn chiến lược không liên quan đến nhau mà bạn đang cố gắng quản lý cùng nhau? – ircmaxell

Trả lời

1

Dựa trên nhận xét và cập nhật của bạn, tôi không nghĩ rằng mã này quá dễ vỡ. Sẽ khó bảo trì hơn nếu bạn thay đổi chuỗi cuộc gọi cho loại chiến lược (do_one, do_two, v.v.) hoặc thêm chiến lược. Thay vào đó, tôi khuyên bạn nên sử dụng abstract factory để tạo "chiến lược". Sau đó, trong mã mà bạn cần chiến lược, hãy tìm chính đối tượng chiến lược ...

Lý do tôi thích phương pháp này tốt hơn là hai lần. Đầu tiên, nó chỉ tạo ra các chiến lược theo yêu cầu, vì vậy bạn không xây dựng các đối tượng mà bạn không cần. Thứ hai, nó đóng gói sự lựa chọn của người dùng vì đó là nơi duy nhất cần tìm kiếm nó (bạn có thể xây dựng nó với việc tiêm phụ thuộc, nhưng bạn cũng cần một nơi khác để quản lý tòa nhà).

class StrategyFactory { 

    protected $strategies = array(); 

    //If you like getter syntax 
    public function __call($method, $arguments) { 
     $method = strtolower($method); 
     if (substr($method, 0, 3) == 'get') { 
      $strategy = substr($method, 3); 
      return $this->getStrategy($strategy); 
     } 
     throw new BadMethodCallException('Unknown Method Called'); 
    } 

    public function getStrategy($strategy) { 
     if (isset($this->strategies[$strategy])) { 
      return $this->strategies[$strategy]; 
     } elseif ($this->makeStrategy($strategy)) { 
      return $this->strategies[$strategy]; 
     } 
     throw new LogicException('Could not create requested strategy'); 
    } 

    protected function makeStrategy($name) { 
     //pick strategy from user input 
     if ($strategyFound) { 
      $this->strategies[$name] = new $strategy(); 
      return true; 
     } else { 
      return false; 
     } 
    } 
} 

Sau đó, sử dụng như sau:

$strategy = $factory->getSomeStrategyName(); 
$strategy->execute(); 

hoặc thậm chí với chaning:

$factory->getSomeStrategyName()->execute(); 

Hoặc không magic method:

$factory->getStrategy('strategyName')->execute(); 
+0

Tôi thích việc triển khai nhà máy mà bạn đã cung cấp ở đây. Cảm ơn! – Stephen

2

Hmm, vâng, tôi không nghĩ nó quá giòn. Bạn không cần các chuỗi mặc dù. Bạn có thể đơn giản sử dụng một mảng được sắp xếp từ khi đặt tên tương ứng với 0,1,2,3 anyway. Nếu bạn lo ngại về các chiến lược hoặc lớp học không hợp lệ đang được cung cấp, bạn có thể đặt một số xác thực vào trình quản lý.

public function __construct() { 
    $this->selectedStrategies = array(
     /* could add some default strategies */ 
    ); 
} 
public function load(array $userStrategies) { 
    for($i=0; $i<3; $i++) { 
     try { 
      $rc = new ReflectionClass($userStrategies[$i]); 
      if($rc->implementsInterface('Iterator')) { 
       $this->selectedStrategies[$i] = new $userStrategies[$i]; 
      } else { 
       throw new InvalidArgumentException('Not a Strategy'); 
      } 
     } catch(ReflectionException $e) { 
      throw new InvalidArgumentException('Not a Class'); 
     } 
    } 
} 

Và thay vì gọi những chiến lược với các phím kết hợp của bạn, bạn chỉ cần

$this->selectedStrategies[0]->execute(); 

và vân vân.


Tuy nhiên, cách tiếp cận khác sẽ được sử dụng

class StrategyCollection 
{ 
    protected $strategies; 

    public function __construct() { 
     $this->strategies = new SplFixedArray(4); 
    } 

    public function add(IStrategy $strategy) { 
     $this->strategies[] = $strategy; 
     return $this; 
    } 
} 

và sau đó điền Manager/Bộ sưu tập từ bên ngoài. Với typehint cho IStrategy bạn có thể chắc chắn chỉ có các lớp thực hiện giao diện chiến lược mới kết thúc trong trình quản lý. Điều đó giúp bạn tiết kiệm được các cuộc gọi Reflection hơi tốn kém khi tạo chiến lược. SplFixedArray đảm bảo có ngoại lệ thời gian chạy khi bạn cố gắng thêm nhiều hơn bốn chiến lược.


Trên sidenote, không tin tưởng đầu vào từ hộp chọn. Chỉ vì một hộp chọn cho các tùy chọn cố định, không có nghĩa là người dùng độc hại không thể tinker với yêu cầu.Tất cả dữ liệu yêu cầu phải được khử trùng và kiểm tra lại.

+1

+1 cho sidenote và lớp StrategyCollection, đơn giản và được xây dựng tốt. – Iiridayn

+1

+1 cho SplFixedArray. Tốt đẹp. – Stephen

0

Nếu các chức năng chiến lược don' t cần nhà nước, bạn có thể chuyển sang một chức năng phong cách lập trình và thay thế toàn bộ lớp học với: call_user_func($strategy['first']); (thứ hai, v.v.). Nếu bạn quan tâm đến không gian tên chung, chúng có thể được lưu trữ như các thành viên tĩnh của một lớp - tức là call_user_func(array('Strategies', $strategy['first'])); Sau đó bạn có thể nhận danh sách tất cả các chiến lược hợp lệ (để tạo và kiểm tra hộp chọn) bằng cách sử dụng get_class_methods('Strategies');. danh sách toàn cầu về các chiến lược hợp lệ.

Nếu bạn cần trạng thái lưu trữ với các chức năng chiến lược - Tôi có thể sử dụng một số loại chức năng bộ nhớ đệm gọi - một cái gì đó giống như

function doStrategy($class) { 
    static $selectedStrategies = array(); 

    if (!isset($selectedStrategies[$class])) { 
     $selectedStrategies[$class] = new $class; 
    } 

    $Strategy = $selectedStrategies[$class]; 
    $Strategy->execute(); // some versions of PHP require two lines here 
} 

Tất nhiên bạn vẫn có thể sử dụng một lớp hơn một chức năng để làm điều này cũng : P.

Biểu thức "Stringly Typed" không được áp dụng cho PHP vì nó vừa được nhập sai và đã sử dụng nội bộ để lưu trữ các ký hiệu (tên lớp và chức năng, biến, v.v.). Như vậy, để phản ánh kiểu dữ liệu chuỗi thường phù hợp nhất. Chúng ta sẽ không đi vào ý nghĩa của ngôn ngữ nói chung.

Các vấn đề liên quan