2011-02-01 9 views
19

"Thay thế có điều kiện với đa hình" chỉ thanh lịch khi loại đối tượng bạn đang thực hiện chuyển đổi/câu lệnh cho đã được chọn cho bạn. Ví dụ, tôi có một ứng dụng web mà đọc một tham số chuỗi truy vấn được gọi là "hành động". Hành động có thể có các giá trị "xem", "chỉnh sửa", "sắp xếp" và v.v. Vậy làm thế nào để tôi thực hiện điều này với đa hình? Vâng, tôi có thể tạo một lớp trừu tượng được gọi là BaseAction và lấy ra ViewAction, EditAction và SortAction từ nó. Nhưng tôi không cần một điều kiện để quyết định hương vị nào của BaseAction để khởi tạo? Tôi không thấy làm thế nào bạn hoàn toàn có thể thay thế điều kiện với đa hình. Nếu bất cứ điều gì, các điều kiện chỉ được đẩy lên đến đỉnh của chuỗi.Thay thế có điều kiện với đa hình - đẹp về mặt lý thuyết nhưng không thực tế

EDIT:

public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

switch (action) 
{ 
    case "view": 
     theAction = new ViewAction(); 
     break; 

    case "edit": 
     theAction = new EditAction(); 
     break; 

    case "sort": 
     theAction = new SortAction(); 
     break; 
} 

theAction.doSomething(); // So I don't need conditionals here, but I still need it to decide which BaseAction type to instantiate first. There's no way to completely get rid of the conditionals. 
+0

vui lòng cung cấp thêm một số mã nếu bạn đang tìm kiếm hướng dẫn đa hình mà bạn có thể tìm thấy ở mọi nơi trên web. Vui lòng cung cấp một số ví dụ. – amirouche

+0

Không thể thêm tất cả mã vào đây. Tôi đã cung cấp một ví dụ dưới đây (tìm Charles) – Charles

+0

Bạn có thể thay thế các điều kiện còn lại bằng cách sử dụng api phản chiếu. Xác định thời gian chạy loại hành động cơ bản nào bạn muốn khởi tạo tùy thuộc vào chuỗi. có một giảm xuống này, chuỗi của bạn phải phù hợp với tên chính xác của hành động cơ sở của bạn. Bạn cũng có thể sử dụng một bản đồ băm để ánh xạ chuỗi với hành động cơ bản và chọn tại thời gian chạy. –

Trả lời

23

Bạn nói đúng - "các điều kiện đang bị đẩy lên đầu chuỗi" - nhưng không có "chỉ" về nó. Nó rất mạnh mẽ. Như @thkala nói, bạn chỉ cần chọn một lần; từ đó trở đi, đối tượng biết làm thế nào để đi về kinh doanh của nó. Cách tiếp cận bạn mô tả - BaseAction, ViewAction và phần còn lại - là một cách hay để thực hiện. Hãy dùng thử và xem mã của bạn trở nên sạch hơn bao nhiêu.

Khi bạn đã có một phương pháp nhà máy lấy chuỗi như "Chế độ xem" và trả về một Hành động và bạn gọi đó là bạn đã cô lập điều kiện của mình. Thật tuyệt. Và bạn không thể đánh giá đúng sức mạnh cho đến khi bạn thử nó - vì vậy hãy cho nó một shot!

+0

Cảm ơn, điều đó có ý nghĩa rất nhiều. – Charles

7

Một số điều cần xem xét:

  • Bạn chỉ nhanh chóng từng đối tượng lần. Một khi bạn làm điều đó, không cần thêm điều kiện về loại của nó.

  • Ngay cả trong trường hợp một lần, bạn sẽ loại bỏ bao nhiêu điều kiện nếu bạn sử dụng các lớp con? Mã sử ​​dụng điều kiện như thế này hoàn toàn dễ bị đầy đủ của cùng một điều kiện chính xác một lần nữa và một lần nữa ...

  • Điều gì sẽ xảy ra khi bạn cần foo Giá trị hành động trong tương lai? Bạn sẽ phải sửa đổi bao nhiêu địa điểm?

  • Nếu bạn cần số bar chỉ khác một chút so với foo thì sao? Với các lớp học, bạn chỉ cần kế thừa BarAction từ FooAction, ghi đè một thứ bạn cần thay đổi.

Trong đoạn mã chạy dài hướng đối tượng nói chung là dễ dàng hơn để duy trì hơn so với mã thủ tục - rất kinh nghiệm sẽ không có một vấn đề với một trong hai, nhưng đối với phần còn lại của chúng ta ở đó một sự khác biệt.

+0

Tôi có thể thấy lợi ích ở hạ lưu. Nhưng một lần nữa, tôi vẫn cần một trường hợp chuyển đổi ở đâu đó để quyết định loại sẽ tạo. – Charles

3

Ví dụ của bạn không yêu cầu đa hình và có thể không được thông báo. Ý tưởng ban đầu của việc thay thế logic có điều kiện bằng công văn đa hình là âm thanh mặc dù.

Đây là sự khác biệt: trong ví dụ của bạn, bạn có một bộ hành động cố định (và được xác định trước). Hơn nữa các hành động không liên quan chặt chẽ theo nghĩa là hành động 'sắp xếp' và 'chỉnh sửa' có ít điểm chung. Đa hình là hơn-architecting giải pháp của bạn.

Mặt khác, nếu bạn có nhiều đối tượng có hành vi chuyên biệt cho một khái niệm chung, tính đa hình chính xác là những gì bạn muốn. Ví dụ, trong một trò chơi có thể có nhiều đối tượng mà người chơi có thể 'kích hoạt', nhưng mỗi đối tượng phản ứng khác nhau. Bạn có thể thực hiện điều này với các điều kiện phức tạp (hoặc nhiều khả năng là một câu lệnh chuyển đổi), nhưng đa hình sẽ tốt hơn. Tính đa hình cho phép bạn giới thiệu các đối tượng và hành vi mới không phải là một phần của thiết kế ban đầu của bạn (nhưng phù hợp với đặc tính của nó).

Trong ví dụ của bạn, sẽ vẫn là một ý tưởng tốt để trừu tượng hóa các đối tượng hỗ trợ hành động xem/chỉnh sửa/sắp xếp, nhưng có lẽ không tự trừu tượng các hành động này. Đây là một thử nghiệm: bạn có bao giờ muốn đặt những hành động đó trong một bộ sưu tập không? Có lẽ không, nhưng bạn có thể có một danh sách các đối tượng hỗ trợ chúng.

3

Có một số cách để dịch chuỗi đầu vào thành đối tượng thuộc một loại nhất định và có điều kiện chắc chắn là một trong số đó. Tùy thuộc vào ngôn ngữ thực hiện, cũng có thể sử dụng câu lệnh chuyển đổi cho phép chỉ định chuỗi mong đợi làm chỉ mục và tạo hoặc tìm nạp đối tượng thuộc loại tương ứng. Vẫn còn có một cách tốt hơn để làm điều đó.

Một bảng tra cứu có thể được sử dụng để lập bản đồ chuỗi đầu vào cho các đối tượng yêu cầu:

action = table.lookup (action_name); // Retrieve an action by its name 
if (action == null) ...    // No matching action is found 

Mã khởi tạo sẽ chăm sóc của việc tạo ra các đối tượng yêu cầu, ví dụ

table ["edit"] = new EditAction(); 
table ["view"] = new ViewAction(); 
... 

Đây là lược đồ cơ bản có thể được mở rộng để bao gồm nhiều chi tiết hơn, chẳng hạn như đối số bổ sung của các đối tượng hành động, bình thường hóa tên hành động trước khi sử dụng chúng để tra cứu bảng, thay thế bảng bằng một mảng đơn giản bằng cách sử dụng số nguyên thay vì chuỗi để xác định tác vụ được yêu cầu Vv

+0

Tôi cũng thích tra cứu/từ điển/băm để chuyển đổi câu lệnh. –

1
public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either 
         // "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

switch (action) 
{ 
    case "view": 
     theAction = new ViewAction(); 
     break; 

    case "edit": 
     theAction = new EditAction(); 
     break; 

    case "sort": 
     theAction = new SortAction(); 
     break; 
} 

theAction.doSomething(); 

Vì vậy, tôi không cần điều kiện ở đây, nhưng tôi vẫn cần nó để quyết định BaseAction loại để nhanh chóng đầu tiên. Không có cách nào để loại bỏ hoàn toàn các điều kiện.

0

Đa hình là một phương pháp ràng buộc. Nó là một trường hợp đặc biệt được gọi là "Mô hình đối tượng". Các mô hình đối tượng được sử dụng để thao tác các hệ thống phức tạp, như mạch hoặc bản vẽ. Hãy xem xét một cái gì đó được lưu trữ/marshalled nó định dạng văn bản: mục "A", kết nối với mục "B" và "C". Bây giờ bạn cần phải biết những gì được kết nối với A. Một chàng trai có thể nói rằng tôi sẽ không tạo ra một mô hình đối tượng cho điều này bởi vì tôi có thể đếm nó trong khi phân tích cú pháp, một lần. Trong trường hợp này, bạn có thể đúng, bạn có thể thoát khỏi mà không có mô hình đối tượng. Nhưng nếu bạn cần phải làm rất nhiều thao tác phức tạp với thiết kế nhập khẩu? Bạn sẽ thao tác nó trong định dạng văn bản hoặc gửi tin nhắn bằng cách gọi các phương thức java và tham chiếu các đối tượng java là thuận tiện hơn? Đó là lý do tại sao nó được đề cập rằng bạn cần phải làm bản dịch chỉ một lần.

1

Tôi đã suy nghĩ về vấn đề này có lẽ nhiều hơn các nhà phát triển còn lại mà tôi đã gặp. Hầu hết trong số họ là hoàn toàn không biết chi phí của việc duy trì lồng nhau nếu-else tuyên bố hoặc chuyển trường hợp. Tôi hoàn toàn hiểu vấn đề của bạn trong việc áp dụng giải pháp được gọi là "Thay thế có điều kiện với đa hình" trong trường hợp của bạn. Bạn thành công nhận thấy rằng đa hình hoạt động miễn là đối tượng đã được chọn. Nó cũng đã được nói trong tread này rằng vấn đề này có thể được giảm xuống để kết hợp [key] -> [class]. Đây là ví dụ AS3 thực hiện các giải pháp.

private var _mapping:Dictionary; 
private function map():void 
{ 
    _mapping["view"] = new ViewAction(); 
    _mapping["edit"] = new EditAction(); 
    _mapping["sort"] = new SortAction(); 
} 

private function getAction(key:String):BaseAction 
{ 
    return _mapping[key] as BaseAction; 
} 

Chạy mà bạn muốn:

public function run(action:String):void 
{ 
    var selectedAction:BaseAction = _mapping[action]; 
    selectedAction.apply(); 
} 

Trong ActionScript3 có một chức năng toàn cầu gọi getDefinitionByName (key: String): Class.Ý tưởng là sử dụng các giá trị khóa của bạn để khớp với tên của các lớp đại diện cho giải pháp cho điều kiện của bạn. Trong trường hợp của bạn, bạn sẽ cần phải thay đổi "xem" thành "ViewAction", "chỉnh sửa" thành "EditAction" và "sort" thành "SortAtion". Không cần phải ghi nhớ bất cứ điều gì bằng cách sử dụng bảng tra cứu. Chức năng chạy sẽ trông giống như sau:

public function run(action:Script):void 
{ 
    var class:Class = getDefintionByName(action); 
    var selectedAction:BaseAction = new class(); 
    selectedAction.apply(); 
} 

Thật không may, bạn có thể linh hoạt để kiểm tra giải pháp này, nhưng bạn linh hoạt thêm hành động mới. Nếu bạn tạo một khóa mới, điều duy nhất bạn cần làm là tạo một lớp thích hợp để xử lý nó.

Vui lòng để lại nhận xét ngay cả khi bạn không đồng ý với tôi.

7

Mặc dù câu trả lời cuối cùng là một năm trước, tôi muốn đưa ra một số nhận xét/nhận xét về chủ đề này.

Đáp Xem

Tôi đồng ý với @CarlManaster về mã hóa các tuyên bố switch một lần để tránh tất cả các vấn đề nổi tiếng đối phó với mã trùng lặp, trong trường hợp này liên quan đến điều kiện (một số trong số họ được đề cập bởi @thkala).

Tôi không tin rằng phương pháp của @ KonradSzałwiński hoặc @AlexanderKogtenkov đề xuất phù hợp với kịch bản này vì hai lý do:

Thứ nhất, từ các vấn đề bạn đã mô tả, bạn không cần phải tự động thay đổi ánh xạ giữa tên của một hành động và thể hiện của một hành động xử lý nó.

Lưu ý các giải pháp này cho phép thực hiện điều đó (bằng cách chỉ định tên hành động cho một thể hiện hành động mới), trong khi giải pháp dựa trên chuyển mạch tĩnh thì không (ánh xạ được mã hóa cứng). Ngoài ra, bạn vẫn sẽ cần một điều kiện để kiểm tra xem một khóa đã cho có được xác định trong bảng ánh xạ hay không, nếu không phải là một hành động (phần default của một câu lệnh chuyển đổi).

Thứ hai, trong ví dụ cụ thể này, dictionaries thực sự ẩn triển khai câu lệnh chuyển đổi. Thậm chí nhiều hơn, có thể dễ dàng đọc/hiểu câu lệnh switch với mệnh đề mặc định hơn là phải thực thi tinh thần mã trả về đối tượng xử lý từ bảng ánh xạ, bao gồm cả việc xử lý khóa không được định nghĩa.

Có một cách bạn có thể thoát khỏi tất cả các điều kiện, trong đó có câu lệnh switch:

Loại bỏ các câu lệnh switch (sử dụng không có điều kiện ở tất cả)

Làm thế nào để tạo ra các đối tượng hành động ngay từ tên hành động?

Tôi sẽ không đọc được ngôn ngữ vì vậy câu trả lời này không nhận được mà dài, nhưng mẹo là nhận ra các lớp là các đối tượng quá.

Nếu bạn đã xác định một hệ thống phân cấp đa hình, không có ý nghĩa gì khi tham chiếu đến phân lớp cụ thể của BaseAction: tại sao không yêu cầu trả lại đúng cá thể xử lý một hành động theo tên của nó?

Điều đó thường được thực hiện bởi cùng một tuyên bố chuyển đổi bạn đã viết (nói, một phương pháp nhà máy) ...nhưng còn về điều này:

public class BaseAction { 

    //I'm using this notation to write a class method 
    public static handlingByName(anActionName) { 
     subclasses = this.concreteSubclasses() 

     handlingClass = subclasses.detect(x => x.handlesByName(anActionName)); 

     return new handlingClass(); 
    } 
} 

Vậy, phương pháp đó là gì?

Đầu tiên, truy xuất tất cả các lớp con cụ thể về điều này (trỏ đến BaseAction). Trong ví dụ của bạn, bạn sẽ lấy lại bộ sưu tập với ViewAction, EditActionSortAction.

Lưu ý rằng tôi đã nói các lớp con cụ thể, không phải tất cả các lớp con. Nếu hệ thống phân cấp sâu hơn, các lớp con cụ thể sẽ luôn là lớp con ở dưới cùng của cấu trúc phân cấp (lá). Đó là bởi vì họ là những người duy nhất không được trừu tượng và cung cấp thực hiện thực tế.

Thứ hai, nhận phân lớp con đầu tiên trả lời có thể xử lý hành động theo tên của nó hay không (tôi đang sử dụng ký hiệu lambda/đóng cửa). Một thi mẫu của phương pháp handlesByName lớp cho ViewAction sẽ trông như thế:

public static class ViewAction { 

    public static bool handlesByName(anActionName) { 
     return anActionName == 'view' 
    } 

} 

Thứ ba, chúng tôi gửi các tin nhắn mới đến lớp để xử lý các hành động, tạo ra hiệu quả một thể hiện của nó.

Tất nhiên, bạn phải xử lý trường hợp khi không có lớp con nào xử lý tác vụ theo tên của nó. Nhiều ngôn ngữ lập trình, bao gồm Smalltalk và Ruby, cho phép truyền phương thức phát hiện lambda/closure thứ hai sẽ chỉ được đánh giá nếu không có lớp con nào phù hợp với tiêu chí. Ngoài ra, bạn sẽ phải đối phó với trường hợp nhiều hơn một phân lớp xử lý hành động theo tên của nó (có thể, một trong những phương thức này đã được mã hóa theo cách sai).

Kết luận

Một ưu điểm của phương pháp này là hành động mới có thể được hỗ trợ bằng cách viết (và không thay đổi) mã hiện tại: chỉ cần tạo một lớp con mới của BaseAction và thực hiện các phương pháp handlesByName lớp một cách chính xác. Nó có hiệu quả hỗ trợ thêm một tính năng mới bằng cách thêm một khái niệm mới, mà không cần sửa đổi sự xâm nhập hiện tại. Rõ ràng là, nếu tính năng mới yêu cầu một phương thức đa hình mới được thêm vào hệ thống phân cấp, các thay đổi sẽ là cần thiết. Ngoài ra, bạn có thể cung cấp cho nhà phát triển bằng cách sử dụng phản hồi hệ thống của bạn: "Hành động được cung cấp không được xử lý bởi bất kỳ lớp con nào của BaseAction, vui lòng tạo một lớp con mới và triển khai phương pháp trừu tượng". Đối với tôi, thực tế là bản thân mô hình cho bạn biết điều gì sai (thay vì cố gắng thực hiện tinh thần một bảng tra cứu) thêm giá trị và hướng dẫn rõ ràng về những gì phải được thực hiện.

Có, điều này nghe có vẻ quá thiết kế. Hãy giữ một tâm trí cởi mở và nhận ra rằng liệu một giải pháp được thiết kế quá mức hay không phải làm, trong số những thứ khác, với văn hóa phát triển của ngôn ngữ lập trình cụ thể mà bạn đang sử dụng. Ví dụ, .NET guys có lẽ sẽ không sử dụng nó vì .NET không cho phép bạn coi các lớp là các đối tượng thực, trong khi mặt khác, giải pháp đó được sử dụng trong các nền văn hóa Smalltalk/Ruby.

Cuối cùng, hãy sử dụng cảm giác và sở thích chung để xác định trước nếu một kỹ thuật cụ thể thực sự giải quyết được vấn đề của bạn trước khi sử dụng. Nó là hấp dẫn có, nhưng tất cả các thương mại-off (văn hóa, thâm niên của các nhà phát triển, sức đề kháng để thay đổi, mở tâm trí, vv) nên được đánh giá.

0

Bạn có thể lưu trữ chuỗi và loại hành động tương ứng ở đâu đó trong bản đồ băm.

public abstract class BaseAction 
{ 
    public abstract void doSomething(); 
} 

public class ViewAction : BaseAction 
{ 
    public override void doSomething() { // perform a view action here... } 
} 

public class EditAction : BaseAction 
{ 
    public override void doSomething() { // perform an edit action here... } 
} 

public class SortAction : BaseAction 
{ 
    public override void doSomething() { // perform a sort action here... } 
} 


string action = "view"; // suppose user can pass either 
         // "view", "edit", or "sort" strings to you. 
BaseAction theAction = null; 

theAction = actionMap.get(action); // decide at runtime, no conditions 
theAction.doSomething();