iT邦幫忙

0

深入淺出設計守則3

  • 分享至 

  • xImage
  •  

深入淺出設計守則3

麥當雞公司要推出一系列的促銷廣告,
因為每天麥當雞公司的產品, 不是全部都有上架,
主管希望促銷廣告依照當天有產品上架時, 就投放該產品的廣告,

  • 尚未登入麥當雞系統的客戶只看到A 系列的廣告
  • 登入系統進去的客戶則只看到B 系列的廣告.
  • 另外針對已登入的客戶, 要投放優惠期限的C 促銷廣告.
  • 每一種廣告的超連結也不一樣.
  • 即使是同一種廣告, 登入前和已登入的客戶的促銷連結也不一定一樣

小明依照上述需求寫了

public class Banner 
{
   public Banner(bool isSignedIn, FoodList foodList, Customer customer)
   {
      _isSignedIn = isSignedIn;
      _foodList = foodList;
      _customer = customer;
   }

   public void SetBannerAction(string bannerName)
   {
      if (string.IsNullOrEmpty(bannerName))
      {
         SetWithNoAction();
         return;
      }

      var isFoodCode = int.TryParse(bannerName, out food);
      if (!_foodList.TryGetInShelves(food, out var food))
      {
         SetWithNoAction();
         return;
      }

      switch (bannerName.Trim().ToLower())
      {
         case "ChickenNuggets":
            LinkUrl = "www.mcdelchicken.com/1.aspx";
            return;

         case "FrenchFries":
            LinkUrl = "www.mcdelchicken.com/2.aspx";
            return;         

         default:
            LinkUrl = "www.mcdelchicken.com/3.aspx"
            return;
      }

      if (IsAGroupCustomers())
      {
            LinkUrl = _isSignedIn
               ? "mcdelchicken.com/5.aspx"
               : "mcdelchicken.com/6.aspx";
            return;
      }
   }
}

public List<Banner>() SetBanners(bool isSignedIn, FoodList foodList, Customer customer)
{
   var banners = new List<Banner>();
   if (isSignedIn && IsInFoodPromotionPeriod())
   {
      var banner = new TiBEBanner(isSignedIn, _foodList, customer);
      banner.SetBannerAction(...);
      banners.Add(banner);
   }
   else if (DateTime.Now < DateTime.Parse("2019-06-10"))
   {
      var banner = new TiBEBanner(isSignedIn, _foodList, customer);
      banner.SetBannerName(...);
      Banners.Add(banner);
   }
   ...
   return banners;
}

可以改進的地方

觀察上面的程式碼發現

  • SetBanners() 方法中分類已登入/未登入的廣告, 也檢查是否目前是否在促銷活動期間?
  • Banner 物件內試圖分類已登入/未登入/某些食物的促銷連結.

維護缺點就是

  • 假如我們要新增或移除規則就很難維護
  • 新增或修改某些廣告的促銷連結不易

簡單的鴨子版本

首先我們先針對廣告種類做分類, 這問題就像模擬鴨子問題.
我們應該定義一個基本的廣告物件

public interface IFoodBanner
{
   string BannerName { get; }
   string LinkUrl { get; } 
}

然後做一般的廣告的促銷連結

public class GeneralBanner : IFoodBanner
{
   public string BannerName { get; } = "General";
   public LinkUrl { get; } = "www.mcdelchicken.com/3.aspx";
}

再針對某些廣告另外做特別的促銷連結

public class ChickenNuggetsBanner : FoodBanner
{
   public string BannerName { get; } = "ChickenNuggets";
   public override string LinkUrl { get; } = "www.mcdelchicken.com/4.aspx"; 
}

工廠模式

工廠模式的優點就是分離了物件的使用和創造. client 不管你怎麼生成的, 但缺點也很明確,
每當有新的物件出來工廠就要改, 複雜度上升得很快.
Client 的用法都是一樣不需要改, 完全符合開放封閉守則.

然後寫一個新物件專門來生產廣告, 我們稱這個物件為"廣告工廠", 程式碼如下

public class BannerFactory
{
   public IFoodBanner Create(string bannerName)
   {
      if( bannerName == "ChickenNuggets" ) {
         return new ChickenNuggetsBanner();
      }
      return new GeneralBanner();
   }
}

然後在原本舊系統中SetBanners() 裡面使用工廠物件來產生各種促銷廣告

public List<IFoodBanner>() SetBanners(bool isSignedIn, FoodList foodList, Customer customer)
{
   var banners = new List<IFoodBanner>();

   foreach(var food in foodList) {
      banners.Add(_bannerFactory.Create(food.Name));
   }

   ....

   return banners;
}

設計守則 當看到數條規則的時候, 我們應該試著考慮設計一個規則模式(Rules Design Pattern)

規則模式的工作原理是將規則與規則處理邏輯分開(應用單一責任原則).
這樣可以輕鬆添加新規則而無需更改系統的其餘部分(應用打開/關閉原則).

接下來我們開始寫"廣告出現規則",

public interface IShowBannerRule
{
   List<IFoodBanner> Filter(List<IFoodBanner> banners, bool isSignedIn, Customer customer);
}

我們可以設計一個 "促銷活動期間才出現的廣告" 的規則

public class PeriodShowBannerRule : IShowBannerRule
{
   public List<IFoodBanner> Filter(List<IFoodBanner> banners, bool isSignedIn, Customer customer)
   {
      if (isSignedIn && IsInFoodPromotionPeriod())
      {
         banners.Add(_bannerFactory.Create(...));
      }
      else if (DateTime.Now < DateTime.Parse("2019-06-10"))
      {
         banners.Add(_bannerFactory.Create(...));
      } 
      return banners;
   } 
} 

再設計另一條規則 "已登入客戶才看到的廣告"

public class SignedInShowBannerRule : IShowBannerRule
{
   public List<IFoodBanner> Filter(List<IFoodBanner> banners, bool isSignedIn, Customer customer)
   {
      ....
   }
}

設計下一條規則 "未登入客戶才看到的廣告"... 以此類推....

最後在原本舊系統中SetBanners() 裡面使用規則來新增/隱藏廣告

public List<IFoodBanner>() SetBanners(bool isSignedIn, FoodList foodList, Customer customer)
{
   var banners = new List<IFoodBanner>();

   foreach(var food in foodList) {
      banners.Add(_bannerFactory.Create(food.Name));
   }

   var rules = new [] {
      new PeriodShowBannerRule(),
      new SignedInShowBannerRule(),
      new LogoutRuleShowBanner()
   };

   foreach (var rule in rules)
   {
      banners = rule.Filter(banners, isSignedIn, customer);
   }

   return banners;
}

善用工廠模式和規則模式, 這樣一來SetBanners() 方法內容流程就比較清晰也比較容易維護理解.


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言