设计模式的概念
设计模式是经过高度抽象化的在编程中可以被反复使用的代码设计经验的总结。
正确使用设计模式能提高代码的可读性、可重用性和可靠性,编写符合设计模式规范的代码不但有利于自身系统的稳定、可靠,还有利于外部系统的对接。在使用了良好设计模式的系统工程中,无论是对满足当前的需求还是对适应未来的需求,无论是对自身系统间模块的对接还是对外部系统的对接,都有很大帮助。
设计模式的七个原则
-
单一职责原则
单一职责原则又称单一功能原则,它规定一个类只有一个职责。如果有多个职责(功能)设计在一个类中,这个类就违反了单一职责原则。 -
开闭原则
开闭原则规定软件中的对象(类、模块、函数等)对扩展开放,对修改封闭,这意味着一个实体允许在不改变其源代码的前提下改变其行为,该特性在产品化的环境下是特别有价值的,在这种环境下,改变源代码需要经过代码审查,单元测试等过程以确保产品的使用质量。遵循这个原则的代码在扩展时并不发生改变,因此不需要经历上述过程。 -
里氏代换原则
里氏代换原则是对开闭原则的补充,规定了在任意父类可以出现的地方,子类都一定可以出现。实现开闭原则的关键就是抽象化,父类与子类的继承关系就是抽象化的具体表现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 -
依赖倒转原则
依赖倒转原则指程序要依赖于抽象(Java中的抽象类和接口),而不依赖于具体的实现(Java中的实现类)。简单地说,就是要求对抽象进行编程,不要求对实现进行编程,这就降低了用户与实现模块之间的耦合度。 -
接口隔离原则
接口隔离原则是指通过将不同的功能定义在不同的接口中来实现接口的隔离,这样就避免了其他类在依赖该接口(接口上定义的功能)时依赖其不需要的接口,可减少接口之间依赖的冗余性和复杂性。 -
合成/聚合复用原则
合成/聚合复用原则指通过在一个新的对象中引入(注入)已有的对象以达到类的功能复用和扩展的目的。它的设计原则是要尽量使用合成或聚合而不要使用继承来扩展类的功能。 -
迪米特法则
迪米特法则指一个对象尽可能少地与其他对象发生相互作用,即一个对象对其他对象应该有尽可能少的了解或依赖。其核心思想在于降低模块之间的耦合度,提高模块的内聚性。迪米特法则规定每个模块对其它模块都要有尽可能少的了解和依赖,因此很容易使系统模块之间的功能独立,这使得各个模块的独立运行变得更加简单,同时使得各个模块之间的组合变得更加容易。
设计模式的分类
设计模式按照其功能和使用场景可分为三大类:
序号 | 设计模式 | 说明 | 包含的设计模式 |
---|---|---|---|
1 | 创建型模式 | 提供了多种优雅创建对象的方法 | 工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式 |
2 | 结构型模式 | 通过类和接口之间的继承和引用实现创建复杂结构对象的功能 | 适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式、代理模式 |
3 | 行为型模式 | 通过类之间不同的通信方式实现不同的行为方式 | 责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板模式、访问者模式 |
工厂模式 Factory Pattern
工厂模式是最常见的设计模式,该模式属于创建型模式,它提供了一种简单、快速、高效而安全地创建对象的方式。工厂模式在接口中定义了创建对象的方法,而将具体的创建对象的过程在子类中实现,用户只需通过接口创建需要的对象即可,不用关注对象的具体创建过程。同时,不同的子类可根据需求灵活实现创建对象的不同方法。
通俗地讲,工厂模式的本质就是用工厂方法代替new操作创建一种实例化对象的方式,以便提供一种方便地创建有同种类型接口地产品的复杂对象。
以创建老婆为例,假设老婆的类型有36D和75B两种类型,我们要实现的是根据不同的传入参数实例化不同的老婆,具体实现如下:
(1)定义接口
//定义了一个Wife接口,并在接口中定义了cup(),用来返回老婆的cup
public interface Wife {
String cup();
}
(2)定义实现类
//定义了两个Wife的实现类BigWife和SmallWife来表示两个cup的老婆,两个cup的老婆通过实现cup()打印自己的cup
public class BigWife implements Wife {
@Override
public String cup() {
return "this is a 36D wife";
}
}
public class SmallWife implements Wife {
@Override
public String cup() {
return "this is a 75B wife";
}
}
(3)定义工厂类
/**
定义了名为Factory的工厂类,工厂类有一个方法createWife(),用来根据不同的参数实例化不同cup的老婆类并返回
在createWife的参数为"bigWife"时,工厂类为我们实例化一个BigWife类的实例并返回
在createWife的参数为"smallWife"时,工厂类为我们实例化一个SmallWife类的实例并返回
这样便实现了工厂类通过不同的参数创建不同的实例,对调用者来说屏蔽了实例化的细节
*/
public class Factory {
public Wife createWife(String wifeName){
if("bigWife".equals(wifeName))
return new BigWife();
else if("smallWife".equals(wifeName))
return new SmallWife();
else
return null;
}
}
(4)使用工厂模式:
//定义了一个Factory实例,并调用createWife()根据不同的参数创建了cup为75B的实例和cup为36D的实例,并分别调用cup()打印不同的cup信息
public static void main(String[] args) {
Factory factory=new Factory();
Wife smallWife = factory.createWife("smallWife");
Wife bigWife = factory.createWife("bigWife");
System.out.println(smallWife.cup());
System.out.println(bigWife.cup());
}
(5)运行结果如下:
抽象工厂模式 Abstract Factory Pattern
抽象工厂模式在工厂模式上添加了一个创建不同工厂的抽象接口(抽象类或接口实现),该接口可叫做超级工厂。在使用过程中,我们首先通过抽象接口创建出不同的工厂对象,然后根据不同的工厂对象创建不同的对象。
我们可以将工厂模式理解为针对一个产品维度进行分类,比如上述工厂模式下的SmallWife和BigWife,而抽象工厂针对的是多个产品维度分类,比如苹果公司既制造苹果手机也制造ipad,同样华为公司也制造华为手机和华为的平板电脑。
在同一个厂商有多个维度的产品时,如果使用工厂模式,则势必会存在多个独立的工厂,这样的话设计和物理世界是不对应的。正确的做法是通过抽象工厂模式来实现,我们可以将抽象工厂类比成厂商,将通过抽象工厂创建出来的工厂类比成不同产品的生产线,在需要生产产品时根据抽象工厂生产。
以两个工厂都要创建手机和电脑为例:
(1)第一类产品的手机接口及实现类:
public interface Phone {
String call();
}
public class ApplePhone implements Phone {
@Override
public String call() {
return "这是苹果手机";
}
}
public class HuaweiPhone implements Phone {
@Override
public String call() {
return "这是华为手机";
}
}
(2)第一类产品的手机工厂类定义如下:
public class PhoneFactory extends AbstractFactory {
@Override
public Phone createPhone(String brand) {
if("Huawei".equals(brand))
return new HuaweiPhone();
else if("Apple".equals(brand))
return new ApplePhone();
return null;
}
@Override
public Computer createComputer(String brand) {
return null;
}
}
(3)第二类产品的电脑接口及实现类的定义如下:
public interface Computer {
String internet();
}
public class AppleComputer implements Computer {
@Override
public String internet() {
return "使用苹果电脑上网";
}
}
public class HuaweiComputer implements Computer {
@Override
public String internet() {
return "使用华为电脑上网";
}
}
(4)第二类产品的电脑工厂类定义如下:
public class ComputerFactory extends AbstractFactory {
@Override
public Phone createPhone(String brand) {
return null;
}
@Override
public Computer createComputer(String brand) {
if("Huawei".equals(brand))
return new HuaweiComputer();
else if("Apple".equals(brand))
return new AppleComputer();
return null;
}
}
(5)抽象工厂定义如下:
这个类是抽象工厂的核心类,它定义了两个方法,用户在需要手机时调用createPhone()构造一个手机即可,用户在需要电脑时调用其createComputer()构造一个电脑即可。
public abstract class AbstractFactory {
public abstract Phone createPhone(String brand);
public abstract Computer createComputer(String brand);
}
(6)使用抽象工厂
public static void main(String[] args) {
AbstractFactory phoneFactory=new PhoneFactory();
Phone huaweiphone = phoneFactory.createPhone("Huawei");
Phone applephone = phoneFactory.createPhone("Apple");
System.out.println(huaweiphone.call());
System.out.println(applephone.call());
System.out.println("------------------------");
AbstractFactory computerFactory=new ComputerFactory();
Computer huaweicomputer = computerFactory.createComputer("Huawei");
Computer applecomputer = computerFactory.createComputer("Apple");
System.out.println(huaweicomputer.internet());
System.out.println(applecomputer.internet());
}
以上代码使用我们定义好的抽象工厂,在需要生产产品时,首先需要定义一个抽象工厂的工厂类,然后使用抽象的工厂类生产不同的工厂类,最终根据不同的工厂生产不同的产品。运行结果如下:
单例模式 Singleton Pattern
单例模式是保证系统实力唯一性的重要手段。单例模式首先通过将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获取类的实例,用户只需也只能通过调用该方法获取类的实例。
单例模式的设计保证了一个类在整个系统中同一时刻只有一个实例存在,主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。同时单例模式为系统资源的优化提供了很好的思路,频繁创建或销毁对象都会增加系统的资源消耗,而单例模式保障了整个系统只有一个对象能被使用,很好地节约了资源。
单例模式的实现很简单,每次在获取对象前都判断系统是否已经有这个单例对象,有则返回,无则创建。需要注意的是,单例模型的类构造器是私有的,只能由自身创建和销毁对象,不允许除了该类的其他程序使用new关键字创建对象及破坏单例模式。
懒汉模式(线程安全)
懒汉模式很简单,定义一个私有的静态对象,之所以定位为静态是因为静态属性是属于类的,能够很好地保障单例对象的唯一性,然后定义一个加锁的静态方法获取该对象,如果该对象为null,则定义一个对象实例并将其赋值给instance,这样下次获取时就有值了。
public class LazySingleton {
//私有属性存放实例对象
private LazySingleton instance;
//私有构造器
private LazySingleton(){}
//获取实例的方法
public synchronized LazySingleton getLazySingleton(){
if(instance!=null)
return instance;
instance=new LazySingleton();
return instance;
}
}
饿汉模式
饿汉模式指在类中直接定义全局的静态对象的实例并初始化,然后提供一个方法获取该实例对象。懒汉模式和饿汉模式最大的不同在于,懒汉模式在类中定义了单例但是并未实例化,实例化的过程是在获取单例对象的方法中定义的,也就是说在第一次调用懒汉模式时,该对象一定为空,然后实例化对象并赋值。而饿汉模式是在定义单例对象时就将其实例化,也就是说在饿汉模式下,在类加载完成后该类的实例已经存在于JVM中了。
public class HungrySingleton {
//私有属性存放实例对象
private HungrySingleton instance=new HungrySingleton();
//私有构造器
private HungrySingleton(){}
//获取实例的方法
public synchronized HungrySingleton getHungrySingleton(){
return instance;
}
}
静态内部类
静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性。
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
双重校验锁
双锁模式指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized(Lock2Singleton.class)给单例类加索来保障操作的唯一性。
public class Lock2Singleton {
private volatile static Lock2Singleton singleton;
private Lock2Singleton(){}
public static Lock2Singleton getSingleton(){
if(singleton==null){
synchronized (Lock2Singleton.class){
if(singleton==null){
singleton=new Lock2Singleton();
return singleton;
}
}
}
return singleton;
}
}
建造者模式 Builder Pattern
建造者模式使用多个简单的对象创建一个复杂的对象,用于将一个复杂的构建与其表示分离,使得同样的构建过程可以创建不同的表示,然后通过一个Builder类(该Builder类是独立于其他对象的)创建最终的对象。
建造者模式主要用于解决软件系统中复杂对象的创建问题,比如有些复杂对象的创建需要通过各部分的子对象用一定的算法构成,在需求变化时这些复杂对象将面临很大的改变,不利于系统稳定。但是使用建造者模式能将它们各部分的算法包装起来,在需求变化后只需调整各个算法的组合方式和顺序,能极大提供系统稳定性。建造者模式常被用于一些基本部件不会变而其组合经常变化的应用场景下。
建造者模式与工厂模式的最大区别是,建造者模式更关注产品的组合方式和装配顺序,而工厂模式关注产品的生产本身。
建造者模式在设计时有以下几种角色:
- Builder 创建一个复杂产品对象的抽象接口
- ConcreteBuilder Builder接口的实现类,用于定义复杂产品各个部件的装配流程
- Director 构造一个使用Builder接口的对象
- Product 表示被构造的复杂对象,ConcreteBuilder定义了该复杂对象的装配流程,而Product定义了该复杂对象的结构和内部表示
以生产一个电脑为例,电脑的生产包括cpu、memory、disk等生产过程,这些生产过程对顺序不敏感,这里的Product角色就是电脑。我们还需要定义生产电脑的Builder,ConcreteBuilder和Director ,具体实现如下:
(1)定义需要生产的产品Computer
public class Computer {
private String cpu;
private String memory;
private String disk;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getDisk() {
return disk;
}
public void setDisk(String disk) {
this.disk = disk;
}
}
(2)定义抽象接口ComputerBuilder来描述产品构造和装配的过程
public interface ComputerBuilder {
void buildCpu();
void buildMemory();
void buildDisk();
Computer buildComputer();
}
以上代码定义了ComputerBuilder接口来描述电脑的组装过程,具体包括组装Cpu的方法buildCpu()、组装内存的方法 buildMemory()和组织磁盘buildDisk()的方法,等这些都生产和组装都完成后,就可以调用buildComputer()组装一台完整的电脑了。
(3)定义ComputerBuilder接口的实现类ComputerConcreteBuilder以实现构造和装配该产品的各个组件:
public class ComputerConcreteBuilder implements ComputerBuilder {
private Computer computer;
public ComputerConcreteBuilder(){
computer=new Computer();
}
@Override
public void buildCpu() {
System.out.println("build cpu");
computer.setCpu("8core");
}
@Override
public void buildMemory() {
System.out.println("build memory");
computer.setMemory("16g");
}
@Override
public void buildDisk() {
System.out.println("build disk");
computer.setDisk("1tb");
}
@Override
public Computer buildComputer() {
return computer;
}
}
(4)定义ComputerDirector使用Builder接口实现产品的装配
public class ComputerDirector {
public Computer constructComputer(ComputerBuilder computerBuilder){
computerBuilder.buildCpu();
computerBuilder.buildMemory();
computerBuilder.buildDisk();
return computerBuilder.buildComputer();
}
}
以上代码定义了ComputerDirector来调用ComputerBuilder接口实现电脑的组装,具体组装顺序为buildCpu(),buildMemory(),buildDisk(),buildComputer()。该类是建造者模式对产品生产过程的封装,在需求发生变化且需要先装配完磁盘再装配CPU时,只需调整Director的执行顺序即可,每个组件的装配都稳定不变。
(5)构建Computer
public static void main(String[] args) {
ComputerDirector computerDirector=new ComputerDirector();
ComputerBuilder computerBuilder=new ComputerConcreteBuilder();
Computer computer = computerDirector.constructComputer(computerBuilder);
System.out.println(computer.getCpu());
System.out.println(computer.getMemory());
System.out.println(computer.getDisk());
}
以上代码首先定义了一个ComputerDirector和ComputerBuilder,为构建Computer做好准备,然后通过调用ComputerDirector的constructComputer()实现产品Computer的构建,运行结果如下:
原型模式 Prototype Pattern
原型模式指通过调用原型实例的Clone方法或其他手段来创建对象。
原型模式属于创建型设计模式,它以当前对象为原型来创建另一个新的对象,而无需知道创建的细节。原型模式在Java中通常使用Clone技术实现,在JavaScript中通常使用对象的原型属性实现。原型模式的Java实现很简单,只需要原型类实现Cloneable接口并重写clone方法即可。
Java中的复制分为浅复制和深复制,浅复制:Java中的浅复制是通过实现Cloneable接口并重写clone方法实现。在浅复制的过程中,对象的基本数据类型的变量值会重新被复制和创建,而引用数据类型仍指向原对象的引用,也就是说浅复制不复制对象的引用数据类型;深复制:在深复制的过程中,不论是基本数据类型还是引用数据类型,都会被重新复制和创建。简而言之,深复制彻底复制了对象的数据,浅复制的复制不彻底(忽略了引用数据类型)。
(1)浅复制
public class Computer implements Cloneable {
private String cpu;
private String memory;
private String disk;
public Computer(String cpu, String memory, String disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", disk='" + disk + '\'' +
'}';
}
@Override
public Object clone(){
try {
return (Computer) super.clone();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
(2)深复制
public class ComputerDetail implements Cloneable {
private String cpu;
private String memory;
private Disk disk;
public ComputerDetail(String cpu, String memory, Disk disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
@Override
public String toString() {
return "ComputerDetail{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", disk=" + disk +
'}';
}
@Override
public Object clone(){
try {
ComputerDetail computerDetail= (ComputerDetail) super.clone();
computerDetail.disk= (Disk) this.disk.clone();
return computerDetail;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
class Disk implements Cloneable{
private String ssd;
private String hhd;
public Disk(String ssd, String hhd) {
this.ssd = ssd;
this.hhd = hhd;
}
@Override
public String toString() {
return "Disk{" +
"ssd='" + ssd + '\'' +
", hhd='" + hhd + '\'' +
'}';
}
@Override
public Object clone(){
try{
return super.clone();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
以上代码定义了ComputerDeatil和Disk两个类,其中ComputerDeatil的disk属性是一个引用对象,要实现这种对象的复制,就要使用深复制技术,具体操作是引用对象类需要实现Cloneable接口并重写clone()方法,然后再复杂对象中声明式地将引用对象复制出来赋值给引用对象地属性,具体代码:computerDetail.disk= (Disk) this.disk.clone();
(3)使用原型模型:
public static void main(String[] args) {
Computer computer=new Computer("8core","16GB","1TB");
System.out.println("浅复制前:"+computer);
Computer computer1 = (Computer) computer.clone();
System.out.println("浅复制后: "+computer1);
System.out.println("--------------------");
Disk disk=new Disk("208G","2Tb");
ComputerDetail computerDetail=new ComputerDetail("12core","64G",disk);
System.out.println("深复制前: "+computerDetail);
ComputerDetail computerDetail1 = (ComputerDetail) computerDetail.clone();
System.out.println("深复制后: "+computerDetail1);
}
运行结果如下:
适配器模式 Adapter Pattern
我们常在开发中遇到各个系统的对接问题,然而每个系统的数据模型或多或少存在差异,因此可能存在修改现有对象模型的情况,这将影响系统稳定。若想在不修改原有代码结构(类的结构)的情况下完成友好对接,就需要用到适配器模式。
适配器模式通过定义一个适配器类作为两个不兼容的接口之间的桥梁,将一个类的接口转换成用户期望的另一个接口,使得两个或多个原本不兼容的接口可以基于适配器类一起工作。
适配器模式主要通过适配器类实现各个接口之间的兼容,该类通过依赖注入或者继承实现各个接口的功能并对外统一提供服务。在适配器模式的实现中有三种角色:source、targetable、adapter。sourc是待适配的类,targetable是目标接口,adapter是适配器。我们在具体应用中通过adapter将source的功能扩展到targetable,以实现接口的兼容。适配器的实现主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
类适配器模式
在需要不改变原有接口或类结构的情况下扩展类的功能以适配不同的接口时,可以使用类的适配器模式。适配器模式通过创建一个继承原有类(需要扩展的类)并实现新接口的适配器类来实现。
(1)定义Source类
待适配的Source类,在该类实现了一个编辑文件的方法editText()
public class Source {
public void editText(){
System.out.println("编辑text文件");
}
}
(2)定义Targetable接口
定义了一个Targetable接口,在该接口定义了两个方法,其中editText是Source中待适配的方法
public interface Targetable {
void editText();
void editWord();
}
(3)定义Adapter继承Source类并实现Targetable接口
适配后的类既可以编辑文本文件,也可以编辑word文件
public class Adapter extends Source implements Targetable{
@Override
public void editWord() {
System.out.println("编辑word文件");
}
}
(4)使用类的适配器
public static void main(String[] args) {
Targetable targetable=new Adapter();
targetable.editText();
targetable.editWord();
}
在使用适配器时只需定义一个实现了Targetable接口的Adapter类并调用target中适配好的方法即可。从运行结果看出适配器不但实现了编辑文本文件的功能,还实现了编辑word文件的功能:
对象适配器模式
对象适配器模式的思路和类适配器模式基本相同,只是修改了Adapter类。Adapter不再继承Source类,而是持有Source类的实例以解决兼容性问题。
(1)适配器类的定义如下:
定义一个适配器类,该适配器实现了Targetable接口并持有Source实例,在适配editText()方法时调用Source实例提供的方法即可。
public class ObjectAdapter implements Targetable {
private Source source;
public ObjectAdapter(Source source){
this.source=source;
}
@Override
public void editText() {
this.source.editText();
}
@Override
public void editWord() {
System.out.println("编辑word文件");
}
}
(2)使用对象适配器模式:
public static void main(String[] args) {
Source source=new Source();
Targetable targetable=new ObjectAdapter(source);
targetable.editText();
targetable.editWord();
}
在使用对象适配器时首先需要定义一个Source实例,初始化时将Source实例作为构造器的参数传递进去,这样就解决了对象的适配,执行结果如下:
接口适配器模式
在不希望实现一个接口中的所有方法时,可以创建一个抽象类AbstractAdapter实现所有方法,在使用时继承该抽象类按需实现方法即可。
(1)定义公共接口Sourceable
public interface Sourceable {
void editText();
void editWord();
}
(2)定义抽象类AbstarctAdapter并实现公共接口的方法
public class AbstarctAdapter implements Sourceable {
@Override
public void editText() {
}
@Override
public void editWord() {
}
}
(3)定义SourceSub1按照需求实现editText()
public class SourceSub1 extends AbstarctAdapter {
@Override
public void editText() {
System.out.println("编辑text文件");
}
}
(4)定义SourceSub2按照需求实现editWord()
public class SourceSub2 extends AbstarctAdapter {
@Override
public void editWord() {
System.out.println("编辑word文件");
}
}
(5)使用接口适配器
public static void main(String[] args) {
Sourceable source1=new SourceSub1();
Sourceable source2=new SourceSub2();
source1.editText();
source2.editWord();
}
使用接口适配器时按照需求实例化不同的子类并调用实现好的方法即可,运行结果:
装饰者模式 Decorator Pattern
装饰者模式指在无需改变原有类及类的继承关系的情况下,动态扩展一个类的功能。它通过装饰者来包裹真实的对象,并动态地向对象添加或者撤销功能。
装饰者模式包括Source和Decorator两种角色,source是被装饰者,decorator是装饰者。装饰者模式通过装饰者可以为被装饰者Source动态地添加一些功能。
(1)定义Sourceable接口
定义了一个Sourceable接口,该接口定义了一个生产老婆地方法。
public interface Sourceable {
public void createWife();
}
(2)定义Sourceable接口的实现类Source
public class Source implements Sourceable {
@Override
public void createWife() {
System.out.println("create wife by Source");
}
}
(3)定义装饰者类Decorator
public class Decorartor implements Sourceable {
private Sourceable source;
public Decorartor(Sourceable source){
this.source=source;
}
@Override
public void createWife() {
source.createWife();
System.out.println("your wife becomes 36D");
}
}
(4)使用装饰者模式
public static void main(String[] args) {
Sourceable source=new Source();
Decorartor decorartor = new Decorartor(source);
decorartor.createWife();
}
在使用装饰者模式时,需要先定义一个待装饰的Source类的实例对象,然后初始化构造器Decorartor并在构造器传入该实例对象,最后调用createWife(),运行结果如下:
代理模式 Proxy Pattern
代理模式指为对象提供一种通过代理的方式来访问并控制该对象行为的方法。在客户端不适合或者不能够直接引用一个对象时,可以通过该对象的代理对象实现对该对象的访问,可以将该代理对象理解为客户端和目标对象之间的中介者。
在代理模式下有两种角色,一种是被代理者,一种是代理(Proxy),在被代理者需要做一项工作时,不用自己做而是交给代理做。以企业招聘为例,不用自己去市场找,可以通过代理去找。
(1)定义Company接口及其实现类Hr:
public interface Company {
void findWorker(String title);
}
public class Hr implements Company {
@Override
public void findWorker(String title) {
System.out.println("我需要找招聘一个员工,岗位是:"+title);
}
}
(2)定义代理类
public class Proxy implements Company {
private Hr hr;
public Proxy(){
this.hr=new Hr();
}
@Override
public void findWorker(String title) {
hr.findWorker(title);
System.out.println("找到了员工:"+getWorker(title));
}
private String getWorker(String title){
Map<String,String> workerList=new HashMap<String,String>(){
{put("Java","james");put("Python","kobe");}
};
return workerList.get(title);
}
}
(3)使用代理模式
public static void main(String[] args) {
Company company=new Proxy();
company.findWorker("Java");
}
在使用代理模式时直接定义一个代理对象并调用其代理的方法即可,运行结果如下:
外观模式 Facade Pattern
外观模式也叫做门面模式,通过一个门面向客户端提供一个访问系统的统一接口,客户端无需关心和知晓系统内部各子模块(系统)之间的复杂关系,其主要目的是降低访问拥有多个子系统的复杂系统的难度,简化客户端与其之间的接口。外观模式将子系统中的功能抽象成一个统一的接口,客户端通过这个接口访问系统,使得系统使用起来更加容易。
简单来说外观模式就是将多个子系统及其之间的复杂关系和调用流程封装到一个统一的接口或类中以对外提供服务,这种模式设计三种角色:子系统角色:实现了子系统的功能;门面角色:外观模式的核心, 熟悉各子系统的功能和调用关系并根据客户端的需求封装统一的方法来对外提供服务;客户角色:通过调用门面来完成业务功能。
以找老婆为例,你只想找一个好看的,学历好的老婆,并不会关注她是不是人工的,毕业证是怎么拿的。
(1)定义整容类
public class MakeFace {
public void start(){
System.out.println("your wife is ugly");
}
public void end(){
System.out.println("your wife is beautiful now!");
}
}
(2)定义学习类
public class MakeStudy {
public void start(){
System.out.println("your wife is fool");
}
public void end(){
System.out.println("your wife is clever now!");
}
}
(3)定义门面类
public class MakeWife {
private MakeFace makeFace;
private MakeStudy makeStudy;
public MakeWife() {
this.makeFace = new MakeFace();
this.makeStudy = new MakeStudy();
}
public void start(){
makeFace.start();
makeStudy.start();
}
public void end(){
makeFace.end();
makeStudy.end();
}
}
(4)使用外观模式
public static void main(String[] args) {
MakeWife makeWife = new MakeWife();
makeWife.start();
makeWife.end();
}
在使用外观模式时,用户只需定义门面类的实例并调用封装好的方法或接口即可,运行结果如下:
桥接模式 Bridge Pattern
桥接模式通过将抽象及其实现解耦,使二者可以根据需求独立变化。这种类型的设计模式属于结构型模式,通过定义一个抽象和实现之间的桥接者来达到解耦的目的。
桥接模型主要用于解决在需求多变的情况下使用继承造成类爆炸的问题,扩展起来不够灵活。可以通过桥接模式将抽象部分与实现部分分离,使其能够独立变化而相互之间的功能不受影响。具体的做法是通过定义一个桥接接口,使得实体类的功能独立于接口实现类,降低他们之间的耦合度。
JDBC和DriverManager就使用了桥接模式,JDBC在连接数据库时,在各个数据库之间切换而不需要修改代码,因为JDBC提供了统一的接口,每个数据库都提供了各自的实现,通过一个叫做数据库驱动的程序来桥接即可。下面以数据库连接为例介绍桥接模式。
(1)定义Driver接口
public interface Driver {
void executeSql();
}
(2)定义MySQL的实现类
public class MysqlDriver implements Driver {
@Override
public void executeSql() {
System.out.println("使用mysql执行sql");
}
}
(3)定义Oracle的实现类
public class OracleDriver implements Driver {
@Override
public void executeSql() {
System.out.println("使用oracle执行sql");
}
}
(4)定义DriverMangerBridge:
public abstract class DriverMangerBridge {
private Driver driver;
public void execute(){
driver.executeSql();
}
public Driver getDriver(){
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
(5)定义MyDriverBridge
public class MyDriverBridge extends DriverMangerBridge {
@Override
public void execute() {
getDriver().executeSql();
}
}
(6)使用桥接模式
public static void main(String[] args) {
DriverMangerBridge driverMangerBridge = new MyDriverBridge();
driverMangerBridge.setDriver(new MysqlDriver());
driverMangerBridge.execute();
driverMangerBridge.setDriver(new OracleDriver());
driverMangerBridge.execute();
}
在以上代码使用了桥接模式,定义了一个DriverMangerBridge,注入不同的驱动器,实现不同的功能,执行结果如下:
组合模式 Composite Pattern
组合模式又叫做部分整体模式,主要用于实现部分和整体操作的一致性。组合模式常根据树形结构来表示部分及整体之间的关系,使得用户对单个对象和组合对象的操作具有一致性。
组合模式通过特定的数据结构简化了部分和整体之间的关系,使得客户端可以像处理单个元素一样来处理整体的数据集,而无需关心单个元素和整体数据集之间的内部复杂结构。
组合模式以类似树形结构的方式实现整体和部分之间关系的组合,下面以实现一个简单的树为例介绍组合模式。
(1)定义TreeNode
public class TreeNode {
private String name;
private TreeNode parent;
private Vector<TreeNode> children=new Vector<>();
public TreeNode(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
public void add(TreeNode node){
children.add(node);
}
public void remove(TreeNode node){
children.remove(node);
}
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
@Override
public String toString() {
return "TreeNode{" +
"name='" + name + '\'' +
", parent=" + parent +
", children=" + children +
'}';
}
}
以上代码定义了TreeNode类表示一个树形结构,并定义了children来存储子类,定义了方法add()和remove()来向树中添加和删除数据。
(2)使用TreeNode
public static void main(String[] args) {
TreeNode nodeA=new TreeNode("A");
TreeNode nodeB=new TreeNode("B");
nodeA.add(nodeB);
System.out.println(nodeA);
}
以上代码演示了TreeNode的使用过程,定义了nodeA和nodeB,并将nodeB作为nodeA的子类,运行结果如下:
享元模式 Flyweight Pattern
享元模式主要通过对象的复用减少对象创建的次数和数量,减少系统内存的使用和降低系统负载。享元模式属于结构型模型,在系统需要一个对象时享元模式首先在系统中查找并尝试重用现有的对象,如果未找到匹配对象则创建新对象并将其缓存在系统中。
享元模式主要用于避免在有大量对象时频繁创建和销毁对象造成系统资源的浪费,把其中共同的部分抽象出来,如果有相同的业务请求则直接返回内存中已有的对象。
下面以内存的申请和使用为例介绍享元模式的使用,创建一个MemoryFactory作为内存管理的工厂,用户通过工厂获得内存,在系统内存池有可用内存时直接获取,如果没有则创建一个内存对象并放入内存池,等下次有相同的内存请求时直接将该内存分配给用户即可。
(1)定义Memory
public class Memory {
private int size;
private boolean isUsed;
private String id;
public Memory(int size, boolean isUsed, String id) {
this.size = size;
this.isUsed = isUsed;
this.id = id;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public boolean isUsed() {
return isUsed;
}
public void setUsed(boolean used) {
isUsed = used;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
(2)定义MemoryFactory工厂
public class MemoryFactory {
private static List<Memory> memoryList=new ArrayList<>();
public static Memory getMemory(int size){
Memory memory=null;
for(int i=0;i<memoryList.size();i++){
memory=memoryList.get(i);
if(memory.getSize()==size&& !memory.isUsed()){
memory.setUsed(true);
memoryList.set(i,memory);
System.out.println("直接获取内存");
break;
}
}
if(memory==null){
memory=new Memory(32,false, UUID.randomUUID().toString());
memoryList.add(memory);
System.out.println("创建新内存");
}
return memory;
}
public static void releaseMemory(String id){
for(int i=0;i<memoryList.size();i++){
Memory memory = memoryList.get(i);
if(memory.getId().equals(id)){
memory.setUsed(false);
memoryList.set(i,memory);
System.out.println("释放内存");
break;
}
}
}
}
以上代码定义了工厂类MemoryFactory,在该类中定义了memoryList用于存储从系统中申请到的内存,该类定义了getMemory,用于从memoryList中获取内存,如果在内存中有空闲的内存直接取出返回,并将该内存的使用状态设置为已使用,如果没有则创建内存并放入内存列表。还定义了释放内存的方法,具体是将内存的使用状态设为false。
(3)使用享元模式
public static void main(String[] args) {
Memory memory=MemoryFactory.getMemory(32);
MemoryFactory.releaseMemory(memory.getId());
MemoryFactory.getMemory(32);
}
运行结果如下: