2 分类
大体上设计模式可以分为5大类,23种。
1. 创建型模式:涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免使用new
创建对象。
创建型模式5种:工厂方法模式,抽象工厂方法模式,单例模式,建造者模式,原型模式。
2. 结构型模式:涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理地使用继承机制;和对象有关的结构型模式涉及如何合理地使用对象组合机制。
结构型模式7种:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
3. 行为型模式:涉及如何合理地设计对象之间的通信、怎样合理地为对象分配职责,让设计更加富有弹性,易维护、易复用。
行为型模式有11种:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。
2.1创建型模式
2.1.1 工厂方法模式
定义一个用于创建对象的接口,至于实例化何种类的对象,则由子类来决定。工厂方法模式使得一个类的实例化延迟到了它的子类。
应用场景
- 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合。
- 用户需要一个类的子类的实例,但用户不知道该类有哪些子类。
优点
- 使用工厂方法模式可以使用户的代码和某个特定的类的子类解耦。
- 工厂方法模式使得用户不用知道子类的实例是如何创建的,只需要知道它有哪些方法即可。
简单工厂模式
简单工厂模式是工厂方法模式的一种,又称静态方法工厂模式,它存在的目的很简单,定义一个用于创建对象的接口。
//工厂
public class SimpleFactory {
static XiaoMiPhone produce(String type) throws Exception {
if (type.equals("XiaoMi5"))
return new XiaoMi5();
else if (type.equals("XiaoMi6"))
return new XiaoMi6();
else
throw new Exception("no such type!");
}
}
//抽象产品
interface XiaoMiPhone{
void run();
}
//具体产品1
public class XiaoMi5 implements XiaoMiPhone{
@Override
public void run() {
System.out.println("XiaoMi5 is running");
}
}
//具体产品2
public class XiaoMi6 implements XiaoMiPhone{
@Override
public void run() {
System.out.println("XiaoMi6 is running");
}
}
从上述代码中可以看到,静态方法工厂是不适合维护的,如果增加新的产品,那么就需要对整个系统进行修改。
在实际的生产过程中,产品可能是一个多层次的树状结构,由于简单工厂模式中只有一个工厂类对应这些产品,所以实现起来比较复杂,下面我们看下工厂方法模式如何解决这个问题。
工厂方法模式去掉了静态方法工厂中的静态属性,使得它可以被子类继承,这样在静态方法工厂里的集中在静态方法上的压力就可以由子类来分担。
我们可以将工厂定义为一个接口,然后由具体的工厂来决定生产什么产品
//抽象工厂接口
public interface IFactory {
Product produce();
}
//抽象产品
public interface Product {
void run();
}
//具体产品1
public class XiaoMi4 implements Product {
@Override
public void run() {
System.out.println("*******************this is XiaoMi4");
}
}
//具体产品2
public class XiaoMi5 implements Product{
@Override
public void run() {
System.out.println("**************this is XiaoMi5");
}
}
//具体产品工厂1
public class XiaoMi4Factory implements IFactory {
@Override
public Product produce() {
return new XiaoMi4();
}
}
//具体产品工厂2
public class XiaoMi5Factory implements IFactory {
@Override
public Product produce() {
return new XiaoMi5();
}
}
Java的Collection框架是工厂方法模式应用的一个很好的例子:
Java中Collection接口的实现都能够通过iterator()方法返回一个迭代器,而不同Collection实现的迭代器都通过内部类的方式对Iterator进行实现的。
//ArrayList的内部类
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
在这里,我们可以将Iterator作为抽象产品,那么具体产品就是Collection接口的实现对Iterator接口的实现,抽象工厂是Collection接口,抽象工厂提供的抽象生产方法就是Iterator iterator()
,具体工厂类就是Collection的实现类。
2.1.2 单例模式
定义:确保一个类只有一个实例,并提供一个访问它的全局访问点。
应用场景
- 当系统需要某个类只有一个实例时
优点
单例模式的唯一实例由它本身控制,可以很好地控制用户何时访问它。
单例模式实现
在使用单例模式时,需要考虑应用的场景是否是多线程的,如果不是,那么单例模式的实现就十分地简单。
public class SingletonExample {
private static SingletonExample singletonExample;
private SingletonExample(){
}
public static SingletonExample getInstance(){
if (singletonExample==null)
singletonExample=new SingletonExample();
return singletonExample;
}
}
这是一个简单的单例模式。如果需要将单例模式应用到多线程的场景中,那么可以将上面的简单单例模式中的getInstance()
方法用synchronized
进行修饰。这不是一个高效的做法,这会导致在程序中只有一个线程可以调用这个方法,其余的线程都在等待阻塞状态。我们可以仅仅对方法中实例化的代码块进行同步
//同步代码块,引入了双重检验锁机制
public class SingletonExample {
private static SingletonExample singletonExample;
private SingletonExample(){
}
public static SingletonExample getInstance(){
if (singletonExample==null){
synchronized(SingletonExample.class){
if (singletonExample==null)
singletonExample=new SingletonExample();
}
}
return singletonExample;
}
}
这种方式依然是有问题的,最好的方法应该是实现静态内部类,用静态内部类持有一个需要被实例化的对象。
public class SingletonExample {
private static SingletonHolder{
private static final SingletonExample INSTANCE=
new SingletonExample ();
}
private SingletonExample(){
}
public static SingletonExample getInstance(){
return SingletonHolder.INSTANCE;
}
}
2.1.3抽象工厂方法模式
定义:提供一个创建一系列或者相互依赖对象的接口,而无需指定它们的具体的类。这里要注意,所谓一个系列说明它们是一个产品族,例如屏幕、CPU、摄像头都是手机的配件,你不能把生产馒头的任务放在这个接口中。
应用场景
- 当一个一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束。例如一个整体的多个配件之间就存在相互联系。
优点
- 它拥有着工厂方法模式所拥有的优点,但它又同时可以在类的内部对产品族进行限制。
- 所谓产品族,它们之间存在着一定的联系,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。
抽象工厂方法模式
我们假设在上述工厂方法模式中,手机生产的过程中还需要配套的充电器。那么这时抽象产品就变成了两个。
//抽象产品1:手机
public interface Product {
void run();
}
//抽象产品2:充电器
public interface Charger{
void charge();
}
那么这时,就应该为每个手机型号生产配套的充电器,先定义一个抽象的产品工厂
//抽象工厂接口
public interface IFactory {
//生产手机
Product produce();
//生产配套充电器
Charger produceCharger();
}
针对每个型号,定义具体产品:
//具体手机产品1
public class XiaoMi4 implements Product {
@Override
public void run() {
System.out.println("*******************this is XiaoMi4");
}
}
//具体手机产品2
public class XiaoMi5 implements Product {
@Override
public void run() {
System.out.println("*******************this is XiaoMi5");
}
}
//具体充电器产品1
public class XiaoMi4Charger implements Charger{
@Override
public void charge() {
System.out.println("*******************XiaoMi4 is charging");
}
}
//具体充电器产品2
public class XiaoMi5Charger implements Charger{
@Override
public void charge() {
System.out.println("*******************XiaoMi5 is charging");
}
}
//具体工厂1
public class XiaoMi5Line implements IFactory{
@Override
public Product produce(){
return new XiaoMi5();
}
@Override
public Charger produceCharger() {
return new XiaoMi5Charger();
}
}
//具体工厂2
public class XiaoMi4Line implements IFactory{
@Override
public Product produce(){
return new XiaoMi4();
}
@Override
public Charger produceCharger() {
return new XiaoMi4Charger();
}
}
抽象产品可以是一个或者多个,如果是一个,那么抽象工厂方法模式就衰变成为了方法工厂模式。
2.1.4 建造者模式
定义:将一个复杂对象的构建与它的表示分离,使同样的构建可以创建不同的表示。
应用场景
- 当系统准备为用户提供一个内部结构复杂的对象,但在构造方法中编写创建代码无法满足需求时就可以使用建造者模式来获取该对象;
- 当某些系统要求构造对象的过程必须独立于创建该对象的类时。
优点
- 建造者模式将创建该对象的过程封装在具体的建造者中,用户调用不同的具体生成器就可以得到对象的不同表示;
- 建造者模式将对象的构造过程从创建该对象的类中分离了出来,用户无需了解该对象的具体组件;
- 可以更为精细有效地控制对象的构造过程,建造者模式将构造对象的过程分成了若干步骤,这就使得程序更加的精细;
- 对象的构造过程与创建对象的类实现了解耦,使得对象的创建更加灵活有弹性;
- 增加新的具体的建造者时,不用修改创建者的代码,满足了开闭原则。
建造者模式
建造者模式的重点在于分离构建算法和具体的构造实现,使得构建算法可以重用。建造者模式由具体产品、抽象建造者、具体建造者、指挥者构成。
定义一个产品
//产品
public class Product{
String name;
String type;
public void setName(String name){
this.name=name;
}
public String getName(String name){
return name;
}
public void setType(String type){
this.type=type;
}
public String getType(String type){
return type;
}
}
抽象建造者
public interface IProductBuilder{
void buildName(String name);
void buildType(String type);
Product build();
}
具体建造者
//具体建造者通过持有一个需要被构造的实例
public class ProductBuilder1 implements IProductBuilder{
private Product product=new Product();
void buildName(String name){
product.setName(name);
}
void buildType(String type){
product.setType(type);
}
public Product build(){
return product;
}
}
最后是指挥者
public class Director{
private IProductBuilder builder;
public Director(IProductBuilder builder){
this.builder=builder;
}
public Product construct(){
builder.buildName();
builder.buildType();
return builder.build();
}
}
2.1.5 原型模式
定义:用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。
应用场景:
- 程序需要从一个对象出发,得到多个与该对象状态相同,并可以独立变化其状态的对象;
- 当对象的创建需要独立于其构造过程及表示时;
- 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,通过该实例复制该原型得到新的实例可能要比重新使用类的构造方法创建新实例更加方便。
优点:
- 当创建类的新的实例代价更大时使用原型模式复制一个已有的实例可以提高创建新实例的效率;
- 可以动态地保存当前对象的状态,在使用时,可以随时使用对象流保存一个当前对象的复制品;
- 可以在运行时创建对象,而无需创建一系列的类和集成结构;
- 可以动态的添加、删除原型的复制品。
原型模式
原型模式要求被创建的对象实现Cloneable
接口,这样就可以通过复制一个实例对象本身来创建一个新的实例,而无需再关注这个对象本身的类型。
考虑一个简历对象,它有一个工作经验的属性:
public class Resume {
public String name;
public Integer age;
public String gender;
public List<String> famMem=new ArrayList<>();
public WorkExperience experience;
public Resume(String name){
this.name=name;
experience=new WorkExperience();
}
public void setName(String name) {
this.name = name;
}
public void setPersonal(String gender,int age,List<String> famMem){
this.gender=gender;
this.age=age;
this.famMem=famMem;
}
public void setExperience(String timeArea,String company){
experience.timeArea=timeArea;
experience.company=company;
}
@Override
protected Resume clone() throws CloneNotSupportedException {
return (Resume) super.clone();
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", famMem=" + famMem +
", experience=" + experience +
'}';
}
public void display(){
System.out.println(this.toString());
}
}
定义工作经验如下:
public class WorkExperience {
public String timeArea="";
public String company="";
}
应用场景:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<String> famMem = new ArrayList<>(); // 家庭成员名单
famMem.add("Papa");
famMem.add("Mama");
// 创建初始简历
Resume resume1 = new Resume("Jobs", famMem);
resume1.setPersonal("Male", 26);
resume1.setExperience("2013/8/1 - 2015/6/30", "Huawei");
// 通过简历1复制出简历2,并对家庭成员和工作经验进行修改
Resume resume2 = resume1.clone();
resume2.setName("Tom");
resume2.famMem.add("Brother");
resume2.setExperience("2015/7/1 - 2016/6/30", "Baidu");
resume1.display();
resume2.display();
}// main
}
在这个案例中,我们需要注意一个问题,这个clone()
方法是浅拷贝。浅拷贝意味着Resume
的引用类型的属性仅仅是将引用传递给了新的变量,在此时resume2
的experience
属性的修改会导致resume1
的experience
属性发生相同的改变,因为本质上它们持有同一个对象实例。
为了能够实现引用类型的拷贝,应该考虑重写clone()
方法:
public Resume clone() throws CloneNotSupportedException{
int age = this.age;
String sex = this.gender;
String name = new String(this.name);
ArrayList<String> famMem = new ArrayList<>(this.famMem);
Resume copy = new Resume(name, famMem);
copy.setPersonal(sex, age);
copy.setExperience(this.experience.timeArea, this.experience.company);
return copy;
}