概念
设计模式(Design pattern): 是软件开发经验的总结,是软件设计中常见问题的典型解决方案。每个模式都像一个蓝图,您可以自定义以解决代码中的特定设计问题。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
历史:1995年,GoF(Gang of Four,四人组)合作出版了《Design Patterns: Elements of Reusable Object-Oriented Software》一书,共收录了23 种设计模式,从此树立了软件设计模式领域的里程碑,人称【GoF设计模式】。
分类
根据其意图和目的,设计模式可以分成三类:
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
行为型模式
这些设计模式特别关注对象之间的通信。
六大原则
1.开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2.里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3.依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4.接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5.迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6.单一职责原则(Single Responsibility Principle,SRP)
规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change
7、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
常用模式
单例模式
类只能产生一个实例,保证全局使用的是同一对象。
- 类只能有一个实例。
- 类必须自己创建自己的唯一实例。
- 类必须给所有其他对象提供这一实例。
单例模式的7种实现方式
第一种:饿汉式。
优点——线程安全的,只有一个实例。避免了线程同步问题。
缺点——类加载时就完成了实例化,没有达到懒加载的目的。如果一直未使用过这个实例,则会造成资源的浪费。
public class SingleObject {
//构造器私有化
private SingleObject() {}
//本类内部创建对象实例
private final static SingleObject instance = new SingleObject();
//提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
第二种:懒汉式一。
you’dian线程不安全,属于延迟初始化,严格意义上它并不算单例模式。
public class SingleObject {
private static Singleton instance;
private SingleObject(){}
public static Singleton SingleObject() {
if (instance == null) {
instance = new SingleObject();
}
return instance;
}
}
第三种:懒汉式二,线程安全,同步方法。
优点——解决了懒汉式一中线程不安全问题。
缺点——效率太低。
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第四种:双重检查锁模式,非线程安全。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
虽然经过了两次判空,保证每次只有一个线程执行new Singleton(),但还是有问题。因为instance = new Singleton()这条语句并不是原子的。
第五种:双重检查锁+volatile模式,线程安全。
与第四种不同的是instance对象用了volatile修饰,会禁止jvm对该对象的指令重排序,从而避免上述问题
public class Singleton {
private static volatile Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return instance;
}
}
第六种:静态内部类
该种方式和饿汉式的区别是做到了懒加载
public class Singleton {
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
第七种:枚举类
最简单的一种,也是最推荐的一种。可以直接使用
public enum Singleton {
INSTANCE;
}
建议:
- 如果在一资源,必须要求延迟初始化,可以使用第六种静态内部类方式
- 如果涉及到反序列化创建对象,可以使用第七种枚举类的方式
- 在不要求延迟初始化,不介意额外浪费一部分空间,可以使用第一种饿汉式方式。
模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
该模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,创建型模式是对类的实例化过程进行抽象,从而将对象的创建和使用分离开。工厂模式属于创建型模式的范畴,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式的核心思想就是把创建对象和使用对象解藕,由工厂负责对象的创建,而用户只能通过接口来使用对象,这样就可以灵活应对变化的业务需求,方便代码管理、避免代码重复。
假设我们在工作中需要将产品a升级为产品A ,如果创建对象的工作是由用户来做,也就是用户通过new a()的形式创建对象,那么为了应对新的产品升级需求,我们还需要找到所有相关代码并将它们改为new A(),这对于很多庞大工程而言就是一项极为繁琐的工作;而通过应用工厂模式,将所有对象创建工作交由工厂管理时,我们就可以直接在工厂中将return new a()改为return new A(),用户仍然可以调用 factory.createProduct()方法而无须更改原本的代码。这样工厂可以通过复用来减少重复代码量,并且用户无需关注创建对象的逻辑。
工厂模式在Java程序员的工作中可以说是无处不在:我们最常用的Spring就是一个Bean工厂,IOC通过BeanFactory对Bean进行管理(可参考上面这张类图);我们使用的日志框架slf4j使用了工厂方法模式;JDK的Calendar使用了简单工厂模式……
下面将结合基本概念和具体示例来详细分析常用的几种工厂模式。
简单工厂模式
顾名思义,简单工厂模式是最简单的一种工厂模式,它定义了一个负责生产对象的工厂类,使用者可以根据不同参数来创建并返回不同子类,这些子类都共用一个接口(即父类)。
结构
简单工厂模式包含三种类,分别是抽象产品类、具体产品类、工厂类,下面分别对各类及它们之间的关系作进一步说明。
使用
我们将简单工厂模式的使用步骤概括为以下几步:
- 创建抽象产品类,并为具体产品定义好一个接口;
- 创建具体产品类,其通过接口来继承抽象产品类,同时也要定义计划生产的每一个具体产品;
- 创建工厂类,其创建的静态方法可以对传入的不同参数做出响应;
- 外界使用者就能调用工厂类的静态方法了,通过传入不同参数来创建不同具体产品类的实例。
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
version-1.0,用户想订购披萨
/**
* 客户类,需要订购披萨的用户
*/
public class Customer {
public static void main(String[] args) {
//创建一个披萨店,调用披萨店的订购披萨方法完成披萨的订购
PizzaStore pizzaStore = new PizzaStore();
pizzaStore.orderPizza();
}
}
public class PizzaStore {
public Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();//准备
pizza.bake();//烘烤
pizza.cut();//切片
pizza.box();//打包
return pizza;
}
}
public class Pizza {
public void prepare() {
System.out.println("准备");
System.out.println("搅拌面团...");
System.out.println("正在添加酱汁...");
System.out.println("添加浇头: ");
}
public void bake() {
System.out.println("在350℃下烘烤25分钟");
}
public void cut() {
System.out.println("把披萨对角切成片");
}
public void box() {
System.out.println("将披萨放在官方披萨店的盒子里");
}
}
version-1.1,可以根据用户的口味提供不同的披萨给他们
public class Customer {
public static void main(String[] args) {
PizzaStore02 pizzaStore02 = new PizzaStore02();
pizzaStore02.orderPizza("cheese");
}
}
public class PizzaStore02 {
public Pizza orderPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) pizza = new CheesePizza();
else if (type.equals("greek")) pizza = new GreekPizza();
else if (type.equals("pepperoni")) pizza = new PepperoniPizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();//装盒子
return pizza;
}
}
public interface Pizza {
public abstract void prepare();
default void bake() {
System.out.println("在350℃下烘烤25分钟");
}
default void cut() {
System.out.println("把披萨对角切成片");
}
default void box() {
System.out.println("将披萨放在官方披萨店的盒子里");
}
}
public class CheesePizza implements Pizza {
public void prepare() {
System.out.println("准备CheesePizza");
System.out.println("搅拌面团...");
System.out.println("正在添加酱汁...");
System.out.println("添加浇头: ");
}
}
如果此时要增加新口味的披萨,就需要打开orderPizza()方法添加代码,这违背对修改关闭,对扩展开放的设计原则。并且代码中大量使用new会造成代码的高耦合。所以我们需要把new对象的操作独立出来。让可变的代码和不变的代码分离。
version-2.0 简单工厂,把创建对象的过程交给工厂来完成
public class Customer {
public static void main(String[] args) {
PizzaStore03 pizzaStore03 = new PizzaStore03(new SimplePizzaFactory());
pizzaStore03.orderPizza("cheese");
}
}
public class PizzaStore03 {
SimplePizzaFactory factory;
public PizzaStore03(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();//装盒子
return pizza;
}
}
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) pizza = new CheesePizza();
else if (type.equals("greek")) pizza = new GreekPizza();
else if (type.equals("pepperoni")) pizza = new PepperoniPizza();
return pizza;
}
}
严格来说,简单工厂不是一个设计模式,更像是一种编程习惯
抽象工厂
如果披萨店要开加盟店,该怎么做?
可以针对不同的地区,创建不同的工厂加盟店
但是在加盟工厂时,发现创建披萨的动作都在工厂中,总部已经不能控制了创建披萨的过程了。
所以总部决定把创建披萨的权利收回到总店里,然后采用直营的披萨店的模式。
version-2.1 把createPizza移到PizzaStore类里
工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样测试程序或用户使用的永远都是超类,而真正创建对象的子类,客户接触不到,这就实现了解耦。
使用到的类
以上代码画出平行视角类关系
工厂方法让类把实例化推迟到子类
public class PizzaTestDrive {
public static void main(String[] args) {
AbstractPizzaStore nyStore = new NYPizzaStore();
AbstractPizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Jackson Ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("James ordered a " + pizza.getName() + "\n");
}
}
public abstract class AbstractPizzaStore {
Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();//装盒子
return pizza;
}
public abstract Pizza createPizza(String type);
}
public class NYPizzaStore extends AbstractPizzaStore{
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese"))
pizza = new NYStyleCheesePizza();
else if (type.equals("pepperoni"))
pizza = new NYStylePepperoniPizza();
else if (type.equals("clam"))
pizza = new NYStyleClamPizza();
else if (type.equals("veggie"))
pizza = new NYStyleVeggiePizza();
return pizza;
}
}
public class ChicagoPizzaStore extends AbstractPizzaStore{
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese"))
pizza = new ChicagoStyleCheesePizza();
else if (type.equals("pepperoni"))
pizza = new ChicagoStylePepperoniPizza();
else if (type.equals("clam"))
pizza = new ChicagoStyleClamPizza();
else if (type.equals("veggie"))
pizza = new ChicagoStyleVeggiePizza();
return pizza;
}
}
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
public void prepare() {
System.out.println("准备 " + name);
System.out.println("搅拌面团...");
System.out.println("正在添加酱汁...");
System.out.println("添加浇头: ");
//此处逻辑照抄就行
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
public void bake() {
System.out.println("在350℃下烘烤25分钟");
}
public void cut() {
System.out.println("把披萨对角切成片");
}
public void box() {
System.out.println("打包。。。。。。");
}
public String getName() {
return name;
}
}
public class NYStyleCheesePizza extends Pizza{
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
//toppings.add("Grated Reggiano Cheese");
}
}
请自己实现:
NYStylePepperoniPizza.java,
NYStyleClamPizza.java,
NYStyleVeggiePizza.java
public class ChicagoStyleCheesePizza extends Pizza{
public ChicagoStyleCheesePizza() {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}
public void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
请你自己实现:
ChicagoStylePepperoniPizza.java
ChicagoStyleClamPizza.java
ChicagoStyleVeggiePizza.java