Khi bạn có một toán tử Switch phức tạp hoặc là một danh sách các câu lệnh if
Nguyên Nhân
Hiếm khi sử dụng toán tử switch và case là một trong những dấu hiệu của lập trình hướng đối tượng
Thường thì code cho một switch có thể nằm rải rác khắp nơi trong chương trình. Khi có một điều kiện mới được thêm vào, bạn sẽ phải tìm đến tất cả các switch và thay thế nó.
Có một quy luật, khi bạn nhìn thấy switch bạn nên nghĩ tới khái niệm đa hình (polymorphism)
Cách khắc phục
1. Cô lập switch
Để cô lập switch
và đặt nó vào đúng class, bạn phải cần tới 2 khái niệm là Extract Method và Move Method
Đoạn logic switch có thể move ra method mới (Extract method)
class Order
{
// ...
public double calculateTotal()
{
double total = 0;
foreach (Product product in getProducts())
{
total += product.quantity * product.price;
}
// Apply regional discounts.
switch (user.getCountry())
{
case "US": total *= 0.85; break;
case "RU": total *= 0.75; break;
case "CN": total *= 0.9; break;
// ...
}
return total;
}
After
class Order
{
// ...
public double calculateTotal()
{
double total = 0;
foreach (Product product in getProducts())
{
total += product.quantity * product.price;
}
total = applyRegionalDiscounts(total);
return total;
}
public double applyRegionalDiscounts(double total)
{
double result = total;
switch (user.getCountry())
{
case "US": result *= 0.85; break;
case "RU": result *= 0.75; break;
case "CN": result *= 0.9; break;
// ...
}
return result;
}
Sau đó chúng ta áp dụng thêm kỹ thuật Move method, để move ra class mới đúng với nhiệm vụ của method đó.
Problem
class Order
{
// ...
public double calculateTotal()
{
double total = 0;
foreach (Product product in getProducts())
{
total += product.quantity * product.price;
}
total = applyRegionalDiscounts(total);
return total;
}
public double applyRegionalDiscounts(double total)
{
double result = total;
switch (user.getCountry())
{
case "US": result *= 0.85; break;
case "RU": result *= 0.75; break;
case "CN": result *= 0.9; break;
// ...
}
return result;
}
Solution
class Order {
// ...
public double calculateTotal()
{
// ...
total = Discounts.applyRegionalDiscounts(total, user.getCountry());
total = Discounts.applyCoupons(total);
// ...
}
class Discounts {
// ...
public static double applyRegionalDiscounts(double total, string country)
{
double result = total;
switch (country)
{
case "US": result *= 0.85; break;
case "RU": result *= 0.75; break;
case "CN": result *= 0.9; break;
// ...
}
return result;
}
public static double applyCoupons(double total) {
// ...
}
2. Thay thế điều kiện switch bằng đa hình
Sau khi xác định được cấu trúc kế thừa, ta sử dụng nguyên tắc đa hình để thay thế điều kiện switch
Problem: Bạn có câu switch thực hiện một số logic dựa vào loại điều kiện đầu vào
public class Bird
{
// ...
public double GetSpeed()
{
switch (type)
{
case EUROPEAN:
return GetBaseSpeed();
case AFRICAN:
return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return isNailed ? 0 : GetBaseSpeed(voltage);
default:
throw new Exception("Should be unreachable");
}
}
}
Solution: Tạo những Subclass tương ứng với từng nhánh của case
. Trong mỗi Subclass khởi tạo một phương thức tương ứng với một điều kiện đó.
public abstract class Bird
{
// ...
public abstract double GetSpeed();
}
class European: Bird
{
public override double GetSpeed()
{
return GetBaseSpeed();
}
}
class African: Bird
{
public override double GetSpeed()
{
return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue: Bird
{
public override double GetSpeed()
{
return isNailed ? 0 : GetBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.GetSpeed();
3. Thay thế parametter thành những method rõ ràng
Problem: bạn truyền vào param và kiểm tra điều kiện dựa trên param đó
void SetValue(string name, int value)
{
if (name.Equals("height"))
{
height = value;
return;
}
if (name.Equals("width"))
{
width = value;
return;
}
Assert.Fail();
}
Solution: Tách ra method riêng để gán giá trị
void SetHeight(int arg)
{
height = arg;
}
void SetWidth(int arg)
{
width = arg;
}
4. Null Object pattern
Probem: Khi thấy một số phương thức return null thay vì object đó, bạn phải check null cho object đó
if (customer == null)
{
plan = BillingPlan.Basic();
}
else
{
plan = customer.GetPlan();
}
Solution: thay vì null, bạn return một kế thừa null object của đối tượng đó
public sealed class NullCustomer: Customer
{
public override bool IsNull
{
get { return true; }
}
public override Plan GetPlan()
{
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = order.customer ?? new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.GetPlan();
Mình sẽ có bài viết chi tiết về vấn đề này sau
Kết luận
Tác dụng là cải thiện tổ chức code của bạn
Khi nào không nên dùng?
- Khi swtich xử lý một hành động đơn giản, thì không có lý do gì phải change code
- Thường thường switch hay được sử dụng bởi Factory design pattern (Factory Method hoặc Abstract Factory) để select một class cụ thể nào đó