什么是设计模式?
设计模式是针对我们软件工程师经常遇到的反复出现的问题的设计级别解决方案。这不是代码。就像对如何解决这些问题和设计解决方案的描述。
使用这些模式被认为是一种很好的做法,因为该解决方案的设计已经过尝试和测试,因此最终代码的可读性更高。设计模式通常是为OOP语言(例如Java)创建和使用的,其中将编写此处的大多数示例。
设计模式的类型
当前发现了大约26个模式(我几乎认为我不会全部完成这些…)。
这26种可分为3种类型:
1.创建:这些模式是为类实例化而设计的。它们可以是类创建模式,也可以是对象创建模式。
2.结构:这些模式是针对类的结构和组成而设计的。这些模式中的大多数的主要目标是在不改变其组成的大部分情况下,增加所涉及类的功能。
3.行为:这些模式是根据一个类与其他类的交流方式设计的。
在这篇文章中,我们将为每种分类类型经历一个基本的设计模式。
类型1:创意-单例设计模式
单例设计模式是一种创建模式,其目的是仅创建一个类的实例,并仅提供对该对象的一个全局访问点。在Java中,此类的一个常用示例是Calendar,您无法在其中创建该类的实例。它还使用自己的getInstance()
方法来获取要使用的对象。
使用单例设计模式的课程将包括:
单例类图
- 一个私有静态变量,保存该类的唯一实例。
- 私有构造函数,因此无法在其他任何地方实例化它。
- 一个公共静态方法,返回类的单个实例。
单例设计有许多不同的实现。今天,我将介绍的实现;
1.立即实例化
2.延迟实例化
3.线程安全的实例化
立即实例化
public class EagerSingleton {
// create an instance of the class.
private static EagerSingleton instance = new EagerSingleton();
// private constructor, so it cannot be instantiated outside this class.
private EagerSingleton() { }
// get the only instance of the object created.
public static EagerSingleton getInstance() {
return instance;
}
}
这种类型的实例化发生在类加载期间,因为变量实例的实例化发生在任何方法之外。如果客户端应用程序根本不使用此类,则会带来严重的缺陷。如果未使用此类,则应急计划是延迟实例化。
延迟实例化
与上述实现没有太大区别。主要区别在于静态变量最初被声明为null,并且仅在getInstance()
-并且仅当-实例变量在检查时保持为null时才在该方法内实例化该静态变量。
public class LazySingleton {
// initialize the instance as null.
private static LazySingleton instance = null;
// private constructor, so it cannot be instantiated outside this class.
private LazySingleton() { }
// check if the instance is null, and if so, create the object.
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这解决了一个问题,但仍然存在另一个问题。如果两个不同的客户端同时访问Singleton类(毫秒)怎么办?好吧,他们将同时检查该实例是否为null,并找到它为true,因此将为两个客户端的每个请求创建该类的两个实例。要解决此问题,将实现线程安全实例化。
(线程)安全是关键
在Java中,关键字sync用于方法或对象上以实现线程安全,因此只有一个线程可以同时访问特定资源。类实例被放置在一个同步块中,因此该方法只能在一个给定时间由一个客户端访问。
public class ThreadSafeSingleton {
// initialize the instance as null.
private static ThreadSafeSingleton instance = null;
// private constructor, so it cannot be instantiated outside this class.
private ThreadSafeSingleton() { }
// check if the instance is null, within a synchronized block. If so, create the object
public static ThreadSafeSingleton getInstance() {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
return instance;
}
}
同步方法的开销很大,并且降低了整个操作的性能。
例如,如果实例变量已被实例化,则每次任何客户端访问该getInstance()
方法时,该synchronized
方法都会运行,并且性能会下降。这只是为了检查instance
变量的值是否为空。如果找到它,则离开该方法。
为了减少这种开销,使用了双重锁定。该检查也将在synchronized
方法之前使用,并且如果该值单独为null,则该synchronized
方法是否运行。
// double locking is used to reduce the overhead of the synchronized method
public static ThreadSafeSingleton getInstanceDoubleLocking() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
现在进入下一个分类。
类型2:结构化-装饰器设计模式
我将为您提供一个小方案,以更好地说明为什么以及在何处应使用Decorator模式。
假设您拥有一家咖啡店,并且像任何新手一样,您仅以两种类型的纯咖啡开始:自家制混合咖啡和深色烘焙。在您的计费系统中,针对不同的咖啡混合物有一个类,它继承了饮料抽象类。人们实际上开始过来喝上您的(尽管很苦?)美味咖啡。然后是上帝禁止的咖啡新星,他们想要糖或牛奶。
现在,您还需要同时在菜单上和不幸的是在计费系统上拥有这两个附加组件。最初,您的IT人员将为这两种咖啡创建一个子类,一种是含糖,另一种是乳。然后,由于客户总是对的,所以说出这些令人恐惧的话:
“请给我加糖的牛奶咖啡吗?”
???
您的计费系统再一次在您的脸上笑了起来。好吧,回到画板……。
然后,IT人员将糖类的牛奶咖啡作为另一个子类添加到每个父级咖啡类中。这个月的剩余时间都很顺利,人们排队喝咖啡,您实际上是在赚钱。??
但是,等等,还有更多!
世界再次与您对立。一个竞争者在马路对面开放,不仅有四种咖啡,而且还有十多种附加品!?
您购买了所有这些以及更多的东西,自己卖出了更好的咖啡,然后才记住,您忘了更新该分散的计费系统。对于所有附加组件的任何和所有组合,您也很可能无法制作无限数量的子类,而新的咖啡混合物也不能。更不用说最终系统的大小了。
是时候实际投资在一个合适的计费系统上了。您会发现新的IT人员,他们实际上知道他们在做什么,他们会说;
“为什么,如果使用装饰器模式,这将变得更加容易并且更小。”
那到底是什么?
装饰器设计模式属于结构类别,该结构类别处理类的实际结构,无论是继承,构图还是两者兼而有之。该设计的目标是在运行时修改对象的功能。这是利用抽象类和具有构成的接口来获得其预期结果的许多其他设计模式之一。
让我们给数学一个机会(颤抖吗?),使这一切成为现实。
服用4种咖啡混合物和10种添加物。如果我们坚持为一种咖啡的所有附加组件的每个不同组合生成子类。那是;
(10–1)²=9²= 81个子类
我们从10中减去1,因为您无法将一个附加组件与另一个相同类型的附加组件组合在一起,糖和糖听起来很愚蠢。那只是一种咖啡的混合物。将81乘以4,您将获得324个不同的子类!
但是在这种情况下,使用装饰器模式仅需要16个类。想要打赌吗?
装饰器设计模式类图
根据咖啡店场景的分类图
如果根据上面的类图来绘制场景,则对于4种咖啡混合物,我们将获得4类,对于每个附加组件,将获得10种,对于抽象组件,将获得1种,对于抽象装饰器,还将获得1种。看!16!现在交出那100美元。(jk,但如果给出……将不会被拒绝……只是说)
从上面可以看到,就像具体的咖啡混合物是饮料抽象类的子类一样,AddOn抽象类也从中继承其方法。插件是其子类,它们继而继承了任何新方法,以便在需要时向基础对象添加功能。
让我们开始编码,以查看使用中的这种模式。
首先创建Abstract饮料类,所有不同的咖啡混合物将继承自:
public abstract class Beverage {
private String description;
public Beverage(String description) {
super();
this.description = description;
}
public String getDescription() {
return description;
}
public abstract double cost();
}
然后添加两个具体的咖啡混合类。
public class HouseBlend extends Beverage {
public HouseBlend() {
super(“House blend”);
}
@Override
public double cost() {
return 250;
}
}
public class DarkRoast extends Beverage {
public DarkRoast() {
super(“Dark roast”);
}
@Override
public double cost() {
return 300;
}
}
AddOn抽象类也继承自Beverage抽象类(有关此内容,请参见下文)。
public abstract class AddOn extends Beverage {
protected Beverage beverage;
public AddOn(String description, Beverage bev) {
super(description);
this.beverage = bev;
}
public abstract String getDescription();
}
现在,这个抽象类的具体实现:
public class Sugar extends AddOn {
public Sugar(Beverage bev) {
super(“Sugar”, bev);
}
@Override
public String getDescription() {
return beverage.getDescription() + “ with Mocha”;
}
@Override
public double cost() {
return beverage.cost() + 50;
}
}
public class Milk extends AddOn {
public Milk(Beverage bev) {
super(“Milk”, bev);
}
@Override
public String getDescription() {
return beverage.getDescription() + “ with Milk”;
}
@Override public double cost() {
return beverage.cost() + 100;
}
}
如您在上面看到的,我们可以将饮料的任何子类传递给AddOn的任何子类,并获得增加的成本以及更新的描述。并且,由于AddOn类本质上是饮料类型,因此我们可以将AddOn传递给另一个AddOn。这样,我们可以将任意数量的附加组件添加到特定的咖啡混合物中。
现在编写一些代码进行测试。
public class CoffeeShop {
public static void main(String[] args) {
HouseBlend houseblend = new HouseBlend();
System.out.println(houseblend.getDescription() + “: “ + houseblend.cost());
Milk milkAddOn = new Milk(houseblend);
System.out.println(milkAddOn.getDescription() + “: “ + milkAddOn.cost());
Sugar sugarAddOn = new Sugar(milkAddOn);
System.out.println(sugarAddOn.getDescription() + “: “ + sugarAddOn.cost());
}
}
最终结果是:
PS这里货币是斯里兰卡卢比
我们能够在咖啡混合物中添加一个以上的附加组件,并成功更新其最终成本和说明,而无需为所有咖啡混合物的每个附加组件组合创建无限子类。
最后,到最后一个类别。
类型3:行为-命令设计模式
行为设计模式着重于类和对象之间如何通信。命令模式的主要重点是灌输相关各方之间较高程度的松散耦合(
……那是什么?
耦合是两个(或更多)彼此交互的类之间进行交互的方式。这些类相互作用时的理想方案是它们之间不严重依赖。那是松散的耦合。因此,对于松散耦合的一个更好的定义是相互关联的类,它们之间的使用最少。
当需要在不自觉地知道您要什么或接收者是谁的情况下发送请求时,就需要使用此模式。
在这种模式下,调用类与实际执行操作的类分离。调用者类仅具有可调用方法execute,当客户端请求时,该方法运行必要的命令。
让我们以一个基本的实际示例为例,在一家高档餐厅点餐。随着流程的进行,您将订单(命令)交给服务员(调用者),然后由服务员将其交给厨师(接收者),这样您就可以得到食物。听起来很简单……但是需要编码的不少。
这个想法很简单,但是编码很简单。
命令设计模式类图
技术方面的操作流程是,您创建一个具体的命令,该命令实现Command接口,要求接收者完成一个动作,并将该命令发送给调用者。调用者是知道何时发出此命令的人。厨师是唯一知道在给出特定命令/命令后该怎么做的人。因此,当运行调用程序的execute方法时,它又使命令对象的execute方法在接收器上运行,从而完成了必要的操作。
我们需要实现的是;
- 接口命令
- 实现Command接口的Order类
- 类服务员(祈求者)
- 类厨师(接待员)
因此,编码如下所示:
厨师,收货人
public class Chef {
public void cookPasta() {
System.out.println(“Chef is cooking Chicken Alfredo…”);
}
public void bakeCake() {
System.out.println(“Chef is baking Chocolate Fudge Cake…”);
}
}
命令,界面
public interface Command {
public abstract void execute();
}
命令,具体命令
public class Order implements Command {
private Chef chef;
private String food;
public Order(Chef chef, String food) {
this.chef = chef;
this.food = food;
}
@Override
public void execute() {
if (this.food.equals(“Pasta”)) {
this.chef.cookPasta();
} else {
this.chef.bakeCake();
}
}
}
服务员,调用者
public class Waiter {
private Order order;
public Waiter(Order ord) {
this.order = ord;
}
public void execute() {
this.order.execute();
}
}
客户
public class Client {
public static void main(String[] args) {
Chef chef = new Chef();
Order order = new Order(chef, “Pasta”);
Waiter waiter = new Waiter(order);
waiter.execute();
order = new Order(chef, “Cake”);
waiter = new Waiter(order);
waiter.execute();
}
}
如您在上面看到的,客户下订单并将接收方设置为厨师。订单被发送给服务员,服务员将知道何时执行该命令(即何时向厨师下达烹饪命令)。当调用者被执行时,Orders的execute方法在接收者上运行(即厨师被赋予了煮意大利面或烤蛋糕的命令)。
快速回顾
在这篇文章中,我们经历了:
- 真正的设计模式是什么
- 不同类型的设计模式以及它们为何不同
- 每种类型都有一个基本或通用的设计模式
我希望这可以帮到你。
在此处找到该帖子的代码存储库。
参考
The 3 Types of Design Patterns All Developers Should Know (with code examples of each)
网友评论