2010-03-31 18 views
6

rồi đoán câu hỏi này trông rất giống:Refactor mã java

What is the best way to replace or substitute if..else if..else trees in programs?

xem xét câu hỏi này ĐÓNG CỬA!


Tôi muốn refactor code mà trông giống như sau:

String input; // input from client socket. 
if (input.equals(x)) { 
    doX(); 
} else if (input.equals(y)) { 
    doY(); 
} else { 
    unknown_command(); 
} 

Nó là mã mà kiểm tra đầu vào từ ổ cắm để thực hiện một số hành động, nhưng tôi không thích if else xây dựng vì mỗi thời gian một lệnh mới được thêm vào máy chủ (mã) một cái mới nếu cần phải thêm vào đó là xấu. Ngoài ra khi xóa lệnh, if else phải được sửa đổi.

+0

'Bản đồ'? Trên thực tế là chỉ cần di chuyển vấn đề về. Mặc dù vậy, tôi sẽ đặt các hàm trong một thể hiện khác và có lẽ trượt trong một 'giao diện'. –

+2

(Psst dòng 2 và 4 thiếu dấu ngoặc đơn đóng.) –

+0

Rất tiếc :). Bạn đúng rồi. Sửa chúng. – Alfred

Trả lời

8

Thu thập các lệnh đó trong một Map<String, Command> trong đó Command là một interface với phương thức execute().

Map<String, Command> commands = new HashMap<String, Command>(); 
// Fill it with concrete Command implementations with `x`, `y` and so on as keys. 

// Then do: 
Command command = commands.get(input); 
if (command != null) { 
    command.execute(); 
} else { 
    // unknown command. 
} 

Để có được một bước xa hơn, bạn có thể xem xét để điền vào bản đồ tự động bằng cách quét cho các lớp thực hiện một giao diện cụ thể (Command trong trường hợp này) hoặc một chú thích cụ thể trong classpath. Google Reflections có thể giúp ích rất nhiều cho việc này.

Cập nhật (từ nhận xét) Bạn cũng có thể cân nhắc việc kết hợp answer of Instantsoup với câu trả lời của tôi. Trong phương thức buildExecutor(), trước tiên hãy lấy lệnh từ Map và nếu lệnh không tồn tại trong Map, hãy thử tải lớp được liên kết và đặt nó trong Map. Sắp xếp tải chậm. Điều này hiệu quả hơn quét toàn bộ classpath như trong câu trả lời của tôi và tạo ra nó mọi lúc như trong câu trả lời của Instantsoup.

+0

Nhưng sau đó tôi phải sửa đổi bản đồ mỗi khi tôi thêm/xóa một lệnh mới? tôi không thể làm điều này một cách năng động hay gì đó? – Alfred

+0

Vâng, tôi nhận ra rằng sau đó cũng như chỉnh sửa nó ngay trước khi tôi nhìn thấy bình luận của bạn :) – BalusC

+0

@Alfred. Bạn có thể thử suy nghĩ, nhưng nó hầu như không phải là lựa chọn tốt nhất. Đi với bản đồ, giữ nó ở một nơi an toàn, nơi dễ dàng để thêm các lệnh mới. – Tom

3

Một cách có thể để có một giao diện ICommand đó là hợp đồng chung cho một lệnh, ví dụ:

public interface ICommand { 
    /** @param context The command's execution context */ 
    public void execute(final Object context); 
    public String getKeyword(); 
} 

Và sau đó bạn có thể sử dụng cơ chế SPI Java tự động khám phá hiện thực khác nhau của bạn và đăng ký chúng trong a Map<String,ICommand> và sau đó thực hiện knownCommandsMap.get(input).execute(ctx) hoặc một cái gì đó giống nhau.

Điều này thực tế cho phép bạn tách dịch vụ của mình khỏi việc triển khai lệnh, thực hiện hiệu quả các plugin đó.

Đăng ký lớp triển khai với SPI được thực hiện bằng cách thêm tệp có tên là tên đủ điều kiện của lớp ICommand của bạn (vì vậy nếu nó nằm trong gói giả, tệp sẽ là META-INF/dummy.ICommand trong đường dẫn lớp học của bạn), sau đó bạn ' sẽ tải và đăng ký chúng dưới dạng:

final ServiceLoader<ICommand> spi = ServiceLoader.load(ICommand.class); 
for(final ICommand commandImpl : spi) 
    knownCommandsMap.put(commandImpl.getKeyword(), commandImpl); 
+0

tính năng này chỉ có sẵn trong JDK 6 –

+0

Bạn có thể giải thích phần SPI tốt hơn một chút không? – Alfred

+0

@fuzzy lollipop: Thực ra nó đã có trong JDK 5, nhưng trong một gói không công khai (sun.misc ...). Nó nên là một phần của JDK 5 lúc đầu. – Romain

3

Làm thế nào về giao diện, nhà máy và phản ánh một chút? Bạn vẫn sẽ cần phải xử lý các ngoại lệ về đầu vào xấu, nhưng bạn sẽ luôn luôn cần phải làm điều này. Với phương pháp này, bạn chỉ cần thêm một thực thi mới của Executor cho một đầu vào mới.

public class ExecutorFactory 
{ 
    public static Executor buildExecutor(String input) throws Exception 
    { 
     Class<Executor> forName = (Class<Executor>) Class.forName(input); 
     return (Executor) executorClass.newInstance(); 
    } 
} 

public interface Executor 
{ 
    public void execute(); 
} 


public class InputA implements Executor 
{ 
    public void execute() 
    { 
     // do A stuff 
    } 
} 

public class InputB implements Executor 
{ 
    public void execute() 
    { 
     // do B stuff 
    } 
} 

mã ví dụ của bạn sau đó trở thành

String input; 
ExecutorFactory.buildExecutor(input).execute(); 
+1

Đó cũng là một ý tưởng hay, nó chỉ có chi phí tạo ra một cá thể mới mỗi lần. – BalusC

+0

Yup Tôi cũng thích ý tưởng này. – Alfred

+1

Tên của người thi hành đến từ đâu?Nếu đó là đầu vào của máy khách/người dùng, một người dùng độc hại có thể khởi tạo bất kỳ lớp nào trên hệ thống. Nếu lớp khởi tạo thay đổi trạng thái trên hệ thống, thì kẻ tấn công sẽ đạt được một số mức kiểm soát đối với hệ thống. Sau đó, sẽ có một điều kiện cuộc đua giữa instantiation và ngoại lệ được ném (giả sử lớp không phải là một phân lớp của Executor), trong đó kẻ tấn công có thể tận dụng lợi thế của bất kỳ chức năng mở ra bởi các lớp khác ... – atk

2

Xây dựng guốc lệnh trên một lớp enum có thể giảm bớt một số mã soạn sẵn. Giả sử rằng x trong input.equals(x) là "XX" và y trong input.equals(y) là "YY"

enum Commands { 
    XX { 
    public void execute() { doX(); }   
    }, 
    YY { 
    public void execute() { doY(); }   
    }; 

    public abstract void execute(); 
} 

String input = ...; // Get it from somewhere 

try { 
    Commands.valueOf(input).execute(); 
} 
catch(IllegalArgumentException e) { 
    unknown_command(); 
} 
+0

Ngoài ra một ý tưởng hay, nhưng điều này là khá chặt chẽ cùng. Bạn không thể cung cấp lệnh từ "bên ngoài" được nữa. – BalusC

1

Bạn nói rằng bạn đang xử lý đầu vào từ một ổ cắm. Bao nhiêu đầu vào? Nó phức tạp thế nào? Nó có cấu trúc như thế nào?

Tùy thuộc vào câu trả lời cho các câu hỏi đó, bạn có thể viết tốt hơn ngữ pháp và để trình tạo trình phân tích cú pháp (ví dụ: ANTLR) tạo mã xử lý đầu vào.

+0

Chỉ là một giao thức đơn giản. Ví dụ như memcached. – Alfred

+1

@Alfred - trong trường hợp memcached, là một giao thức rất đơn giản có hoạt động nửa tá và không có khả năng thay đổi, tôi sẽ sử dụng cấu trúc if-else. Không có lý do để làm cho chế độ mã của bạn phức tạp chỉ để "hướng đối tượng" nó. Tuy nhiên, tôi sẽ tạo một đối tượng để bọc lệnh và/hoặc phản hồi và xử lý tất cả các phân tích cú pháp. Và có lẽ tạo ra một enum để đại diện cho lệnh (điều này sẽ biến chuỗi if-else thành một switch). – Anon

+1

Nếu tôi muốn có thể thay thế hành vi một cách dễ dàng, tôi có thể sử dụng mẫu Phương thức mẫu, tạo các phương thức trừu tượng cho từng hoạt động và để ứng dụng của tôi phản ánh nhanh các lớp con. – Anon

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