深入浅出设计守则3
麦当鸡公司要推出一系列的促销广告,
因为每天麦当鸡公司的产品, 不是全部都有上架,
主管希望促销广告依照当天有产品上架时, 就投放该产品的广告,
小明依照上述需求写了
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() 方法内容流程就比较清晰也比较容易维护理解.