外观设计模式 (C#)






4.84/5 (52投票s)
在 C# 中实现外观设计模式
引言
本文介绍了外观设计模式的优势,并概述了何时应该使用该模式。源代码是用 C# 实现的。
背景

仅仅看房子的外观,观看者不会知道房子的内部建筑模块是如何组合在一起形成这栋大房子的。
同样,在软件设计中,当我们处理一个大型系统时,我们可能经常需要采用“外观”设计模式。当你想从一个点(客户端)调用一个大系统的许多子系统时,就会出现外观设计模式的需求(这里只从软件角度考虑)。根据 GoF 书籍的定义,外观设计模式可以定义为:
定义 (来自 GoF 书籍):- “外观为一组子系统中的接口提供了一个统一的接口。”
因此,如果一个软件系统由多个子系统组成(通常情况就是如此),我们可能希望创建一个“外观”对象,该对象将从一个地方调用所有这些子系统。采用外观模式的主要优点是:
1) 它提供了一个单一的简化接口,因此使子系统更易于使用。
2) 客户端代码不必创建子系统的各种对象。这将由外观对象来处理。
3) 外观对象将子系统与客户端和其他子系统解耦。

使用代码
让我们看一个可以应用外观模式的代码示例。
在线购物商店在将您喜欢的商品运送到您的地址之前,需要处理一系列复杂步骤。这些步骤可能包括更新库存、验证您的收货地址、验证您的信用卡详细信息、应用各种折扣等等。由于所有这些步骤都很复杂,因此假设每个步骤构成了一个更大的系统(在线订购)的子系统。当在线商店执行这些操作时,它可能希望调用一个单一的操作,例如“FinalizeOrder”,该操作将依次调用所有这些子系统。我们可以看出,这可能是一个完美的应用外观模式的地方。因此,我们将创建一个外观对象,该对象将调用子系统,这些子系统是:InventoryUpdate、AddressVerification、Discounting、PayCardVerification、Shipping。试想一下,如果我们不创建外观对象,那么从客户端代码中,我们将创建 5 个不同的对象,这些对象又会执行这些操作。虽然这并没有错,但看起来很混乱。现在试想一下,相同的步骤需要从他们网站上的其他地方执行,或者可能从他们的移动应用程序执行。我们真的想编写那种创建子系统对象并调用它们各自操作的混乱代码吗?这肯定会污染客户端代码。相反,外观对象将为我们提供救援,并使子系统更易于使用。
让我们看看在线购物的代码,这将使事情更清楚。我们首先会看到没有外观的代码,然后是带有外观对象きの代码。下面是在线购物系统的各种子系统,它们是不言自明的。请注意,我将每个类及其方法都做得非常抽象,它们在这里没有任何业务实现,完全是虚拟的。
库存管理子系统。
interface IInventory { void Update(int productId); } class InventoryManager:IInventory { public void Update(int productId) { Console.WriteLine(string.Format("Product# {0} is subtracted from the store's inventory." , productId)); } }
订单验证子系统
interface IOrderVerify { bool VerifyShippingAddress(int pincode); } class OrderVerificationManager:IOrderVerify { public bool VerifyShippingAddress(int pincode) { Console.WriteLine(string.Format("The product can be shipped to the pincode {0}.", pincode)); return true; } }
折扣和成本子系统。
interface ICosting { float ApplyDiscounts(float price,float discountPercent); } class CostManager:ICosting { public float ApplyDiscounts(float price,float discountPercent) { Console.WriteLine(string.Format("A discount of {0}% has been applied on the product's price of {1}", discountPercent,price)); return price - ((discountPercent / 100) * price); } }
支付网关子系统。
interface IPaymentGateway { bool VerifyCardDetails(string cardNo); bool ProcessPayment(string cardNo, float cost); } class PaymentGatewayManager:IPaymentGateway { public bool VerifyCardDetails(string cardNo) { Console.WriteLine(string.Format("Card# {0} has been verified and is accepted.", cardNo)); return true; } public bool ProcessPayment(string cardNo, float cost) { Console.WriteLine(string.Format("Card# {0} is used to make a payment of {1}.",cardNo,cost)); return true; } }
物流子系统。
interface ILogistics { void ShipProduct(string productName, string shippingAddress); } class LogisticsManager:ILogistics { public void ShipProduct(string productName, string shippingAddress) { Console.WriteLine(string.Format("Congratulations your product {0} has been shipped at the following address: {1}", productName, shippingAddress)); } }
下面是另一个类(一种写得不好的数据模型),它将完成我们的系统。
class OrderDetails { public int ProductNo { get; private set; } public string ProductName { get; private set; } public string ProductDescription { get; private set; } public float Price { get; set; } public float DiscountPercent { get; private set; } public string AddressLine1 { get; private set; } public string AddressLine2 { get; private set; } public int PinCode { get; private set; } public string CardNo { get; private set; } public OrderDetails(string productName, string prodDescription, float price, float discount, string addressLine1,string addressLine2, int pinCode,string cardNo) { this.ProductNo = new Random(1).Next(1,100); this.ProductName = productName; this.ProductDescription = prodDescription; this.Price = price; this.DiscountPercent = discount; this.AddressLine1 = addressLine1; this.AddressLine2 = addressLine2; this.PinCode = pinCode; this.CardNo = cardNo; } }
这些子系统基本上构成了更大的系统“在线购物系统”。现在,在线购物系统需要调用这些子系统,让我们看看从客户端调用它们时我们的代码会是什么样子。
class Program { static void Main(string[] args) { // Creating the Order/Product details OrderDetails orderDetails = new OrderDetails("C# Design Pattern Book", "Simplified book on design patterns in C#", 500, 10, "Street No 1", "Educational Area", 1212, "4156213754" ); // Client Code without Facade. // Updating the inventory. IInventory inventory = new InventoryManager(); inventory.Update(orderDetails.ProductNo); // Verfying various details for the order such as the shipping address. IOrderVerify orderVerify = new OrderVerificationManager(); orderVerify.VerifyShippingAddress(orderDetails.PinCode); // Calculating the final cost after applying various discounts. ICosting costManger = new CostManager(); orderDetails.Price=costManger.ApplyDiscounts(orderDetails.Price, orderDetails.DiscountPercent ); // Going through various steps if payment gateway like card verification, charging from the card. IPaymentGateway paymentGateWay = new PaymentGatewayManager(); paymentGateWay.VerifyCardDetails(orderDetails.CardNo); paymentGateWay.ProcessPayment(orderDetails.CardNo, orderDetails.Price); // Completing the order by providing Logistics. ILogistics logistics = new LogisticsManager(); logistics.ShipProduct(orderDetails.ProductName, string.Format("{0}, {1} - {2}.", orderDetails.AddressLine1, orderDetails.AddressLine2, orderDetails.PinCode)); } }
下面是程序的输出。
上面这段代码在代码整洁度方面有很多问题。客户端代码非常混乱。客户端(主程序)必须创建所有子系统的对象才能
调用它们,此外,这使得客户端代码与系统中的各种对象紧密耦合。
为了清理客户端代码,使其不必担心系统中的各种对象,我们将创建一个“外观”对象。外观对象将为我们提供一个简化的
接口,通过该接口可以调用其他接口。
让我们看看外观对象。
class OnlineShoppingFacade { IInventory inventory = new InventoryManager(); IOrderVerify orderVerify = new OrderVerificationManager(); ICosting costManger = new CostManager(); IPaymentGateway paymentGateWay = new PaymentGatewayManager(); ILogistics logistics = new LogisticsManager(); public void FinalizeOrder(OrderDetails orderDetails) { inventory.Update(orderDetails.ProductNo); orderVerify.VerifyShippingAddress(orderDetails.PinCode); orderDetails.Price = costManger.ApplyDiscounts(orderDetails.Price, orderDetails.DiscountPercent); paymentGateWay.VerifyCardDetails(orderDetails.CardNo); paymentGateWay.ProcessPayment(orderDetails.CardNo, orderDetails.Price); logistics.ShipProduct(orderDetails.ProductName, string.Format("{0}, {1} - {2}.", orderDetails.AddressLine1, orderDetails.AddressLine2, orderDetails.PinCode)); } }
因此,外观对象公开了“FinalizeOrder”方法,通过该方法可以调用各种子系统的所有方法。
让我们看看将使用此外观对象调用子系统的客户端代码。
static void Main(string[] args) { // Creating the Order/Product details OrderDetails orderDetails = new OrderDetails("C# Design Pattern Book", "Simplified book on design patterns in C#", 500, 10, "Street No 1", "Educational Area", 1212, "4156213754" ); // Using Facade OnlineShoppingFacade facade = new OnlineShoppingFacade(); facade.FinalizeOrder(orderDetails); Console.ReadLine(); }
下面是我们得到的输出。
输出证实了“外观”对象以完全相同的方式工作,但它使客户端代码整洁,并将客户端代码从处理各种对象和调用与这些对象相关的方法中解放出来。
关注点
这结束了我们关于“外观”模式的文章,到此您可能已经意识到,我们在很多地方都应该使用外观模式,这将使我们的代码具有更好的结构。您可以通过认识到它提供了一个统一的接口来调用其他接口来体会该模式的优点。另外,试想一下,如果我们还需要从在线购物网站的移动应用程序等地方调用上述子系统,我们将直接使用并调用暴露的外观对象,而不是创建子系统的各个对象并从移动应用程序代码中调用它们。
历史
版本 1.0- (2014/04/30)