麥當雞公司要推出一系列的促銷廣告,
因為每天麥當雞公司的產品, 不是全部都有上架,
主管希望促銷廣告依照當天有產品上架時, 就投放該產品的廣告,
小明依照上述需求寫了
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;
}
觀察上面的程式碼發現
維護缺點就是
首先我們先針對廣告種類做分類, 這問題就像模擬鴨子問題.
我們應該定義一個基本的廣告物件
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() 方法內容流程就比較清晰也比較容易維護理解.