Các cách để loại bỏ việc sử dụng chuyển đổi trong mã là gì?
Các cách để loại bỏ việc sử dụng chuyển đổi trong mã là gì?
Câu trả lời:
Các câu lệnh chuyển đổi không phải là một antipotype per se, nhưng nếu bạn đang mã hóa đối tượng được định hướng, bạn nên xem xét liệu việc sử dụng một chuyển đổi có được giải quyết tốt hơn với đa hình thay vì sử dụng một câu lệnh chuyển đổi.
Với đa hình, điều này:
foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;
case "cat":
echo animal.meow();
break;
}
}
trở thành thế này:
foreach (var animal in zoo) {
echo animal.speak();
}
typeof
và câu trả lời này không đề xuất các cách hoặc lý do để khắc phục các câu lệnh chuyển đổi trong các tình huống khác.
Xem Mùi báo cáo chuyển đổi :
Thông thường, các câu lệnh chuyển đổi tương tự nằm rải rác trong một chương trình. Nếu bạn thêm hoặc xóa một mệnh đề trong một chuyển đổi, bạn cũng thường phải tìm và sửa chữa các mệnh đề khác.
Cả Tái cấu trúc và Tái cấu trúc cho các mẫu đều có cách tiếp cận để giải quyết vấn đề này.
Nếu mã (giả) của bạn trông giống như:
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
Mã này vi phạm Nguyên tắc Đóng mở và dễ vỡ đối với mọi loại mã hành động mới đi kèm. Để khắc phục điều này, bạn có thể giới thiệu một đối tượng 'Lệnh':
interface Command {
public void execute();
}
class LoginCommand implements Command {
public void execute() {
// do what doLogin() used to do
}
}
class RequestHandler {
private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
public void handleRequest(int action) {
Command command = commandMap.get(action);
command.execute();
}
}
Nếu mã (giả) của bạn trông giống như:
class House {
private int state;
public void enter() {
switch (state) {
case INSIDE:
throw new Exception("Cannot enter. Already inside");
case OUTSIDE:
state = INSIDE;
...
break;
}
}
public void exit() {
switch (state) {
case INSIDE:
state = OUTSIDE;
...
break;
case OUTSIDE:
throw new Exception("Cannot leave. Already outside");
}
}
Sau đó, bạn có thể giới thiệu một đối tượng 'Bang'.
// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
public HouseState enter() {
throw new Exception("Cannot enter");
}
public HouseState leave() {
throw new Exception("Cannot leave");
}
}
class Inside extends HouseState {
public HouseState leave() {
return new Outside();
}
}
class Outside extends HouseState {
public HouseState enter() {
return new Inside();
}
}
class House {
private HouseState state;
public void enter() {
this.state = this.state.enter();
}
public void leave() {
this.state = this.state.leave();
}
}
Hi vọng điêu nay co ich.
Map<Integer, Command>
không cần chuyển đổi?
Công tắc là một mẫu, cho dù được thực hiện bằng câu lệnh chuyển đổi, nếu chuỗi khác, bảng tra cứu, đa hình oop, khớp mẫu hoặc một cái gì đó khác.
Bạn có muốn loại bỏ việc sử dụng " tuyên bố chuyển đổi " hoặc " mẫu chuyển đổi " không? Cái thứ nhất có thể được loại bỏ, cái thứ hai, chỉ khi có thể sử dụng một mẫu / thuật toán khác và hầu hết thời gian là không thể hoặc đó không phải là cách tiếp cận tốt hơn để làm như vậy.
Nếu bạn muốn loại bỏ câu lệnh chuyển đổi khỏi mã, câu hỏi đầu tiên cần đặt ra là việc loại bỏ câu lệnh chuyển đổi và sử dụng một số kỹ thuật khác có ý nghĩa gì . Thật không may, câu trả lời cho câu hỏi này là tên miền cụ thể.
Và hãy nhớ rằng trình biên dịch có thể thực hiện các tối ưu hóa khác nhau để chuyển đổi các câu lệnh. Vì vậy, ví dụ nếu bạn muốn xử lý tin nhắn một cách hiệu quả, một câu lệnh chuyển đổi là cách tốt nhất để đi. Nhưng mặt khác, chạy các quy tắc kinh doanh dựa trên câu lệnh chuyển đổi có lẽ không phải là cách tốt nhất và ứng dụng nên được nghiên cứu lại.
Dưới đây là một số lựa chọn thay thế để chuyển đổi tuyên bố:
Chuyển đổi bản thân nó không tệ, nhưng nếu bạn có nhiều "công tắc" hoặc "nếu / khác" trên các đối tượng trong phương thức của mình thì đó có thể là một dấu hiệu cho thấy thiết kế của bạn hơi "thủ tục" và các đối tượng của bạn chỉ là giá trị xô. Di chuyển logic đến các đối tượng của bạn, gọi một phương thức trên các đối tượng của bạn và để chúng quyết định cách trả lời thay thế.
Tôi nghĩ cách tốt nhất là sử dụng Bản đồ tốt. Sử dụng từ điển, bạn có thể ánh xạ hầu hết mọi đầu vào sang một số giá trị / đối tượng / hàm khác.
mã của bạn sẽ trông giống như thế nào (psuedo) như thế này:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Mọi người đều yêu thích if else
các khối HUGE . Rất dễ đọc! Tôi tò mò về lý do tại sao bạn muốn loại bỏ các câu lệnh chuyển đổi, mặc dù. Nếu bạn cần một câu lệnh chuyển đổi, có lẽ bạn cần một câu lệnh chuyển đổi. Nghiêm túc mà nói, tôi muốn nói nó phụ thuộc vào những gì mã đang làm. Nếu tất cả các chuyển đổi đang làm là gọi các chức năng (giả sử), bạn có thể vượt qua các con trỏ hàm. Cho dù đó là một giải pháp tốt hơn là tranh cãi.
Ngôn ngữ là một yếu tố quan trọng ở đây cũng, tôi nghĩ.
Tôi nghĩ rằng những gì bạn đang tìm kiếm là Mô hình chiến lược.
Điều này có thể được thực hiện theo một số cách, đã được đề cập trong các câu trả lời khác cho câu hỏi này, chẳng hạn như:
switch
câu lệnh sẽ tốt để thay thế nếu bạn thấy mình thêm trạng thái mới hoặc hành vi mới vào câu lệnh:
trạng thái int; Chuỗi getString () { chuyển đổi (trạng thái) { trường hợp 0: // hành vi cho trạng thái 0 trả về "không"; trường hợp 1: // hành vi cho trạng thái 1 trả lại "một"; } ném IllegalStateException mới (); } nhân đôi getDouble () { chuyển đổi (this.state) { trường hợp 0: // hành vi cho trạng thái 0 trả về 0đ; trường hợp 1: // hành vi cho trạng thái 1 trả lại 1đ; } ném IllegalStateException mới (); }
Thêm hành vi mới yêu cầu sao chép switch
và thêm trạng thái mới có nghĩa là thêm trạng thái khác case
vào mỗi switch
câu lệnh.
Trong Java, bạn chỉ có thể chuyển đổi một số lượng rất hạn chế các kiểu nguyên thủy có giá trị bạn biết khi chạy. Điều này thể hiện một vấn đề trong chính nó: các trạng thái đang được thể hiện dưới dạng số hoặc ký tự ma thuật.
Kết hợp mẫu và nhiều if - else
khối có thể được sử dụng, mặc dù thực sự có cùng các vấn đề khi thêm các hành vi mới và trạng thái mới.
Giải pháp mà những người khác đã đề xuất là "đa hình" là một ví dụ của mẫu Trạng thái :
Thay thế mỗi trạng thái bằng lớp riêng của nó. Mỗi hành vi có phương thức riêng trên lớp:
Nhà nước IState; Chuỗi getString () { trả về trạng thái.getString (); } nhân đôi getDouble () { trả về trạng thái.getDouble (); }
Mỗi khi bạn thêm một trạng thái mới, bạn phải thêm một triển khai IState
giao diện mới. Trong một switch
thế giới, bạn sẽ thêm một case
cho mỗi switch
.
Mỗi lần bạn thêm một hành vi mới, bạn cần thêm một phương thức mới vào IState
giao diện và mỗi cách thực hiện. Đây là gánh nặng tương tự như trước đây, mặc dù bây giờ trình biên dịch sẽ kiểm tra xem bạn có triển khai hành vi mới trên mỗi trạng thái tồn tại trước đó không.
Những người khác đã nói rằng, điều này có thể quá nặng nề, vì vậy tất nhiên có một điểm bạn đạt đến nơi bạn di chuyển từ nơi này sang nơi khác. Cá nhân tôi, lần thứ hai tôi viết một công tắc là điểm mà tôi tái cấu trúc.
nếu khác
Tôi bác bỏ tiền đề rằng chuyển đổi vốn đã xấu mặc dù.
Tại sao bạn muốn Trong tay một trình biên dịch tốt, một câu lệnh chuyển đổi có thể hiệu quả hơn nhiều so với các khối if / khác (cũng như dễ đọc hơn) và chỉ các công tắc lớn nhất mới có khả năng được tăng tốc nếu chúng được thay thế bởi bất kỳ loại nào của cấu trúc dữ liệu tra cứu gián tiếp.
'Switch' chỉ là một cấu trúc ngôn ngữ và tất cả các cấu trúc ngôn ngữ có thể được coi là công cụ để hoàn thành công việc. Như với các công cụ thực tế, một số công cụ phù hợp với một nhiệm vụ hơn một công việc khác (bạn sẽ không sử dụng búa tạ để đưa ra một cái móc tranh). Phần quan trọng là cách "hoàn thành công việc" được xác định. Nó cần phải được bảo trì, nó có cần phải nhanh, có cần mở rộng quy mô không, có cần phải mở rộng hay không.
Tại mỗi điểm trong quy trình lập trình thường có một loạt các cấu trúc và các mẫu có thể được sử dụng: một công tắc, một chuỗi if-if-if, hàm ảo, bảng nhảy, bản đồ với các con trỏ hàm, v.v. Với kinh nghiệm lập trình viên sẽ biết theo bản năng công cụ phù hợp để sử dụng cho một tình huống nhất định.
Phải giả định rằng bất kỳ ai duy trì hoặc xem xét mã ít nhất đều có kỹ năng như tác giả ban đầu để mọi cấu trúc có thể được sử dụng một cách an toàn.
Nếu công tắc ở đó để phân biệt giữa các loại đối tượng khác nhau, có lẽ bạn đang thiếu một số lớp để mô tả chính xác các đối tượng đó hoặc một số phương thức ảo ...
Dành cho C ++
Nếu bạn đang đề cập đến một ví dụ về AbstractFactory tôi nghĩ rằng phương thức registerCreatorFunc (..) thường tốt hơn là yêu cầu thêm một trường hợp cho mỗi câu lệnh "mới" cần thiết. Sau đó, cho phép tất cả các lớp tạo và đăng ký một creatorFunction (..) có thể dễ dàng thực hiện với một macro (nếu tôi dám đề cập). Tôi tin rằng đây là một cách tiếp cận phổ biến nhiều khung làm. Lần đầu tiên tôi nhìn thấy nó trong ET ++ và tôi nghĩ rằng nhiều khung công tác yêu cầu macro DECL và IMPL sử dụng nó.
Trong một ngôn ngữ thủ tục, như C, sau đó chuyển đổi sẽ tốt hơn bất kỳ thay thế nào.
Trong một ngôn ngữ hướng đối tượng, sau đó hầu như luôn có các lựa chọn thay thế khác có sẵn để sử dụng tốt hơn cấu trúc đối tượng, đặc biệt là đa hình.
Vấn đề với các câu lệnh chuyển đổi phát sinh khi một số khối chuyển đổi rất giống nhau xảy ra ở nhiều nơi trong ứng dụng và cần phải thêm hỗ trợ cho một giá trị mới. Một điều khá phổ biến đối với một nhà phát triển là quên thêm hỗ trợ cho giá trị mới vào một trong các khối chuyển đổi nằm rải rác xung quanh ứng dụng.
Với tính đa hình, sau đó một lớp mới thay thế giá trị mới và hành vi mới được thêm vào như là một phần của việc thêm lớp mới. Hành vi tại các điểm chuyển đổi này sau đó được kế thừa từ siêu lớp, được ghi đè để cung cấp hành vi mới hoặc được thực hiện để tránh lỗi trình biên dịch khi siêu phương thức là trừu tượng.
Khi không có sự đa hình rõ ràng đang diễn ra, nó có thể có giá trị thực hiện mô hình Chiến lược .
Nhưng nếu sự thay thế của bạn là một NẾU lớn ... THÌ ... khối ELSE, thì hãy quên nó đi.
Các con trỏ hàm là một cách để thay thế một câu lệnh chuyển đổi khổng lồ, chúng đặc biệt tốt trong các ngôn ngữ nơi bạn có thể nắm bắt các hàm theo tên của chúng và tạo ra các thứ với chúng.
Tất nhiên, bạn không nên buộc các câu lệnh chuyển đổi ra khỏi mã của mình và luôn có khả năng bạn đang làm sai tất cả, điều này dẫn đến các đoạn mã dư thừa ngu ngốc. (Điều này đôi khi không thể tránh khỏi, nhưng một ngôn ngữ tốt sẽ cho phép bạn loại bỏ sự dư thừa trong khi vẫn sạch sẽ.)
Đây là một ví dụ tuyệt vời và chinh phục:
Nói rằng bạn có một thông dịch viên của một số loại.
switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}
Thay vào đó, bạn có thể sử dụng điều này:
opcode_table[*IP](*IP, vm);
... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };
OpcodeFuncPtr opcode_table[256] = {
...
opcode_add,
opcode_not_zero,
opcode_jump,
opcode_default,
opcode_default,
... // etc.
};
Lưu ý rằng tôi không biết cách loại bỏ sự dư thừa của opcode_table trong C. Có lẽ tôi nên đặt câu hỏi về nó. :)
Câu trả lời rõ ràng nhất, độc lập với ngôn ngữ là sử dụng một loạt 'nếu'.
Nếu ngôn ngữ bạn đang sử dụng có con trỏ hàm (C) hoặc có các hàm là giá trị hạng 1 (Lua), bạn có thể đạt được kết quả tương tự như "chuyển đổi" bằng cách sử dụng một mảng (hoặc danh sách) các hàm (con trỏ tới).
Bạn nên cụ thể hơn về ngôn ngữ nếu bạn muốn câu trả lời tốt hơn.
Báo cáo chuyển đổi thường có thể được thay thế bằng một thiết kế OO tốt.
Ví dụ: bạn có một lớp Tài khoản và đang sử dụng câu lệnh chuyển đổi để thực hiện một phép tính khác dựa trên loại tài khoản.
Tôi sẽ đề nghị rằng điều này nên được thay thế bằng một số lớp tài khoản, đại diện cho các loại tài khoản khác nhau và tất cả đều thực hiện giao diện Tài khoản.
Việc chuyển đổi sau đó trở nên không cần thiết, vì bạn có thể xử lý tất cả các loại tài khoản giống nhau và nhờ tính đa hình, phép tính phù hợp sẽ được chạy cho loại tài khoản.
Phụ thuộc vào lý do tại sao bạn muốn thay thế nó!
Nhiều thông dịch viên sử dụng 'gotos tính toán' thay vì các câu lệnh chuyển đổi để thực thi opcode.
Điều tôi nhớ về chuyển đổi C / C ++ là Pascal 'in' và các phạm vi. Tôi cũng ước tôi có thể bật chuỗi. Nhưng những thứ này, trong khi tầm thường để một trình biên dịch ăn, là công việc khó khăn khi được thực hiện bằng cách sử dụng các cấu trúc và các trình vòng lặp và mọi thứ. Vì vậy, ngược lại, có rất nhiều điều tôi ước mình có thể thay thế bằng một công tắc, nếu chỉ có công tắc của C () linh hoạt hơn!
Chuyển đổi không phải là một cách tốt để đi vì nó phá vỡ Hiệu trưởng Mở Đóng. Đây là cách tôi làm điều đó.
public class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public virtual void Speak()
{
Console.WriteLine("Hao Hao");
}
}
public class Cat : Animal
{
public virtual void Speak()
{
Console.WriteLine("Meauuuu");
}
}
Và đây là cách sử dụng nó (lấy mã của bạn):
foreach (var animal in zoo)
{
echo animal.speak();
}
Về cơ bản những gì chúng tôi đang làm là giao trách nhiệm cho lớp trẻ thay vì cha mẹ quyết định phải làm gì với trẻ.
Bạn cũng có thể muốn đọc lên "Nguyên tắc thay thế Liskov".
Trong JavaScript sử dụng mảng Associative:
this:
function getItemPricing(customer, item) {
switch (customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case
default:
return item.price * item.quantity;
}
}
trở thành thế này:
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
Một phiếu khác cho nếu / khác. Tôi không phải là một fan hâm mộ lớn của các trường hợp hoặc tuyên bố chuyển đổi bởi vì có một số người không sử dụng chúng. Mã ít đọc hơn nếu bạn sử dụng case hoặc switch. Có thể không ít người đọc đối với bạn, nhưng với những người chưa bao giờ cần sử dụng lệnh.
Các nhà máy đối tượng cũng vậy.
Nếu / khối khác là một cấu trúc đơn giản mà mọi người đều có. Có một vài điều bạn có thể làm để đảm bảo rằng bạn không gây ra sự cố.
Thứ nhất - Đừng thử và thụt lề nếu câu lệnh nhiều hơn một vài lần. Nếu bạn thấy mình thụt lề, thì bạn đã làm sai.
if a = 1 then
do something else
if a = 2 then
do something else
else
if a = 3 then
do the last thing
endif
endif
endif
Thực sự là xấu - làm điều này thay vào đó.
if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif
Tối ưu hóa bị nguyền rủa. Nó không tạo ra nhiều sự khác biệt so với tốc độ mã của bạn.
Thứ hai, tôi không phản đối việc thoát ra khỏi Khối nếu miễn là có đủ các câu lệnh ngắt nằm rải rác trong khối mã cụ thể để làm cho nó rõ ràng
procedure processA(a:int)
if a = 1 then
do something
procedure_return
endif
if a = 2 then
do something else
procedure_return
endif
if a = 3 then
do something more
procedure_return
endif
end_procedure
EDIT : On Switch và tại sao tôi nghĩ thật khó để mò mẫm:
Đây là một ví dụ về câu lệnh switch ...
private void doLog(LogLevel logLevel, String msg) {
String prefix;
switch (logLevel) {
case INFO:
prefix = "INFO";
break;
case WARN:
prefix = "WARN";
break;
case ERROR:
prefix = "ERROR";
break;
default:
throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
}
System.out.println(String.format("%s: %s", prefix, msg));
}
Đối với tôi, vấn đề ở đây là các cấu trúc điều khiển thông thường áp dụng trong các ngôn ngữ C giống như đã bị phá vỡ hoàn toàn. Có một quy tắc chung là nếu bạn muốn đặt nhiều hơn một dòng mã trong cấu trúc điều khiển, bạn sử dụng dấu ngoặc nhọn hoặc câu lệnh bắt đầu / kết thúc.
ví dụ
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Đối với tôi (và bạn có thể sửa tôi nếu tôi sai), câu lệnh Case ném quy tắc này ra khỏi cửa sổ. Một khối mã được thực hiện có điều kiện không được đặt bên trong cấu trúc bắt đầu / kết thúc. Vì điều này, tôi tin rằng Case đủ khác biệt về mặt khái niệm để không được sử dụng.
Hy vọng rằng câu trả lời câu hỏi của bạn.