什么是设计模式?
设计模式是我们软件工程师经常遇到的重复问题的设计级解决方案。这不是代码 - 我再说一遍, ? CODE。它就像是关于如何解决这些问题并设计解决方案的描述。
使用这些模式被认为是一种很好的做法,因为解决方案的设计经过了相当多的尝试和测试,从而提高了最终代码的可读性。设计模式通常是为 OOP 语言(如 Java)创建和使用的,从这里开始的大部分示例都将在其中编写。
设计模式的类型
目前发现了大约 26 种模式(我几乎不认为我会全部完成……)。
这 26 种可分为 3 种类型:
\1. Creational:这些模式是为类实例化而设计的。它们可以是类创建模式或对象创建模式。
\2. 结构:这些模式是针对类的结构和组成而设计的。大多数这些模式的主要目标是增加所涉及的类的功能,而不改变它的大部分组成。
\3. 行为:这些模式的设计取决于一个班级与其他班级的交流方式。
在这篇文章中,我们将为每种分类类型介绍一种基本设计模式。
类型 1:Creational - 单例设计模式
单例设计模式是一种创建模式,其目标是只创建一个类的一个实例,并只为该对象提供一个全局访问点。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 类,精确到毫秒怎么办?好吧,他们将同时检查实例是否为空,并发现它为真,因此将为两个客户端的每个请求创建该类的两个实例。为了解决这个问题,将实施线程安全实例化。
(线程)安全是关键
在 Java 中,关键字 synchronized 用于方法或对象以实现线程安全,这样一次只有一个线程会访问特定资源。类实例化放置在同步块中,以便在给定时间只能由一个客户端访问该方法。
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:结构 - 装饰器设计模式
我将为您提供一个小场景,以便更好地了解您应该使用装饰器模式的原因和位置。
假设您拥有一家咖啡店,并且像任何新手一样,您从两种普通咖啡开始,即混合咖啡和深烘焙咖啡。在您的计费系统中,有一个用于不同咖啡混合物的类,它继承了饮料抽象类。人们实际上开始来喝你美妙的(尽管很苦?)咖啡。然后是咖啡新手,上帝保佑,他们想要糖或牛奶。对咖啡的讽刺!??
现在,您还需要将这两个附加组件添加到菜单中,不幸的是在计费系统中。最初,您的 IT 人员将为两种咖啡创建一个子类,一种包括糖,另一种包括牛奶。然后,由于客户永远是对的,人们会说这些可怕的话:
“请给我一杯加糖的牛奶咖啡好吗?”
???
你的计费系统又在你脸上笑了。好吧,回到绘图板......
然后,IT 人员将加糖的牛奶咖啡作为另一个子类添加到每个父咖啡类。这个月剩下的时间一帆风顺,人们排队喝咖啡,你实际上在赚钱。??
但是等等,还有更多!
世界再次与你作对。一个竞争对手在街对面开张,不仅有 4 种咖啡,还有 10 多个附加组件!
您购买所有这些以及更多,以自己销售更好的咖啡,然后记住您忘记更新那个冗长的计费系统。您很可能无法为所有附加组件的任何和所有组合创建无限数量的子类,也包括新的咖啡混合物。更不用说最终系统的大小了。
是时候真正投资于适当的计费系统了。您会找到新的 IT 人员,他们实际上知道自己在做什么,他们会说,
“为什么,如果它使用装饰器模式,这将变得更容易和更小。”
那到底是什么?
装饰器设计模式属于结构类别,它处理类的实际结构,无论是通过继承、组合还是两者兼而有之。此设计的目标是在运行时修改对象的功能。这是利用抽象类和接口与组合来获得所需结果的许多其他设计模式之一。
让我们给 Math 一个机会(不寒而栗?)将这一切纳入视野。
服用 4 份混合咖啡和 10 份添加物。如果我们坚持为一种咖啡的所有附加组件的每种不同组合生成子类。那是:
(10–1)2 = 92 = 81 个子类
我们从 10 中减去 1,因为您不能将一个附加组件与另一个相同类型的附加组件组合在一起,糖和糖听起来很愚蠢。这只是一种混合咖啡。将81 乘以 4,您将得到多达324个不同的子类!谈论所有的编码......
但是使用装饰器模式,在这种情况下它只需要 16 个类。想要打赌吗?
装饰器设计模式类图
根据咖啡店场景的类图
如果我们根据上面的类图绘制我们的场景,我们会为 4 种咖啡混合物获得 4 个类,每个附加组件有 10 个,抽象组件有 1 个,抽象装饰器有 1 个。看!16!现在交出那100美元。??(jk,但如果给予它不会被拒绝......只是说)
从上面可以看出,正如具体的咖啡混合物是饮料抽象类的子类一样,AddOn 抽象类也继承了它的方法。作为其子类的附加组件继而继承任何新方法,以便在需要时向基础对象添加功能。
让我们开始编码,看看这个模式的使用。
首先创建抽象饮料类,所有不同的咖啡混合物都将继承自:
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;
}
}
正如您在上面看到的,我们可以将 Beverage 的任何子类传递给 AddOn 的任何子类,并获得增加的成本以及更新的描述。而且,由于 AddOn 类本质上是 Beverage 类型,我们可以将一个 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 类
- 一个类 Waiter(调用者)
- A类厨师(接收者)
所以,编码是这样的:
厨师,接收者
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 的执行方法在接收器上运行(即,厨师被给予命令来烹饪意大利面?或烘烤蛋糕?)。