设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。本文将对23种设计模式每个都进行一个简单的说明和实现。
设计模式很大程度上是面向对象设计原则的体现,首先来看一下这些oop设计原则:
创建类的设计模式
一、工厂方法模式
工厂父类(接口)负责定义产品对象的公共接口,而子类工厂则负责创建具体的产品对象。是为了把产品的实例化。
代码例子,我们需要生产Bike和Bus,都会run,但是run的方式不一样:
package 工厂方法模式;
interface Car {
void run();
}
class Bike implements Car {
@Override
public void run() {
System.out.println("自行车run");
}
}
class Bus implements Car {
@Override
public void run() {
System.out.println("公交车run");
}
}
//定义父类工厂接口
interface ICarFactory {
Car getCar();
}
//Bike子类工厂接口
class BikeFactory implements ICarFactory {
@Override
public Car getCar() {
return new Bike();
}
}
//Bus子类工厂接口
class BusFactory implements ICarFactory {
@Override
public Car getCar() {
return new Bus();
}
}
public class Main {
public static void main(String[] args) {
//创建Bike工厂
ICarFactory bikeFactory = new BikeFactory();
//获取Bike实例
Car bike = bikeFactory.getCar();
bike.run();//自行车run
//创建Bus工厂
ICarFactory busFactory = new BusFactory();
//获取Bus实例
Car bus = busFactory.getCar();
bus.run();//公交车run
}
}
优点:解耦、灵活、类层次清晰、扩展性好(增加或者修改一个子工厂类就可以修改产品)、屏蔽了产品类,不需要调用者关心产品是如何生产的、迪米特法则,依赖倒置原则,里氏替换原则的体现。
缺点:增加了一个类层次。
二、建造者模式
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
代码例子,我们需要构建一个MyClass对象,这个对象的属性有很多,attr1,attr2,attr3...,为了节省篇幅,我们只写三个。而且这些属性中的某些还有默认值,我们如果使用构造方法重载实现会特别复杂,而且代码不易读。如果使用getter/setter也会导致代码出现长篇幅的getter/setter。我们使用构建者模式解决这个问题:
package 构建器模式;
class MyClass {
private int attr1;
private int attr2;
private int attr3;
public MyClass(Builder builder){
attr1 = builder.attr1;
attr2 = builder.attr2;
attr3 = builder.attr3;
}
//构建器
static class Builder {
private final int attr1;
private int attr2 = 2;
private int attr3 = 3;
//修改属性 返回this
Builder(int attr1){
this.attr1 = attr1;
}
public Builder attr2(int attr2){
this.attr2 = attr2;
return this;
}
public Builder attr3(int attr3){
this.attr3 = attr3;
return this;
}
//调用MyClass构造函数传入Builder对象构建 返回MyClass对象
public MyClass Build(){
return new MyClass(this);
}
}
@Override
public String toString(){
return "attr1:"+attr1+" attr2:"+attr2+" attr3:"+attr3;
}
}
public class Main {
public static void main(String[] args) {
//设定必要的参数attr1为1,其他的为默认值
MyClass obj1 = new MyClass.Builder(1).Build();
System.out.println(obj1);//attr1:1 attr2:2 attr3:3
//设定必要的参数attr1为1,其他的参数也对应做出修改
MyClass obj2 = new MyClass.Builder(1).attr2(20).attr3(30).Build();
System.out.println(obj2);//attr1:1 attr2:20 attr3:30
}
}
这样代码显然在构建过程中更加易读,没有长篇的getter/setter,也不需要记重载参数的顺序和个数(比如子中obj2的构建过程attr3()方法和attr2()方法的调用可以更改顺序)。
使用建造者模式的好处:
1.使用建造者模式可以使客户端不必知道产品内部组成的细节。
2.具体的建造者类之间是相互独立的,对系统的扩展非常有利。
3.由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
使用建造者模式的场合:
1.创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
2.要创建的复杂对象的算法,独立于该对象的组成部分,也独立于组成部分的装配方法时。
三、抽象工厂模式
提供一个创建一系列或相关依赖对象的接口,而无需指定他们具体的类。
抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。
代码例子,我们需要A和B两种枪和子弹,一种枪不能用另一种子弹:
package 抽象工厂模式;
//子弹接口
interface Bullet {
}
class ABullet implements Bullet {
}
class BBullet implements Bullet {
}
//枪接口
interface Gun {
void shot(Bullet bullet);
}
class AGun implements Gun {
@Override
public void shot(Bullet bullet) {
System.out.println("AGun shot with Abullet");
}
}
class BGun implements Gun {
@Override
public void shot(Bullet bullet) {
System.out.println("BGun shot with Bbullet");
}
}
//武器工厂
interface WeaponFactory {
Gun getGun();
Bullet getBullet();
}
class AWeaponFactory implements WeaponFactory {
@Override
public Gun getGun() {
return new AGun();
}
@Override
public Bullet getBullet() {
return new ABullet();
}
}
class BWeaponFactory implements WeaponFactory {
@Override
public Gun getGun() {
return new BGun();
}
@Override
public Bullet getBullet() {
return new BBullet();
}
}
public class Main {
public static void main(String[] args) {
//创建A类武器工厂 创建A类产品族
AWeaponFactory aWeaponFactory = new AWeaponFactory();
Bullet aBullet = aWeaponFactory.getBullet();
Gun aGun = aWeaponFactory.getGun();
aGun.shot(aBullet);
//创建B类武器工厂 创建B类产品族
BWeaponFactory bWeaponFactory = new BWeaponFactory();
Bullet bBullet = bWeaponFactory.getBullet();
Gun bGun = bWeaponFactory.getGun();
bGun.shot(bBullet);
}
}
我们有两个产品族,保证同一种工厂只能生产对应的产品。
当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。
四、单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建。
代码例子,使用三种方式创建单例:
package 单例模式;
class Singleton1 {
private static Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}
public void run(){
System.out.println("单例运行");
}
}
class Singleton2 {
private static volatile Singleton2 instance ;
private Singleton2(){
}
public static Singleton2 getInstance(){
if(instance == null){
synchronized(Singleton2.class){
instance = new Singleton2();
}
}
return instance;
}
public void run(){
System.out.println("单例运行");
}
}
enum Singleton3 {
INSTANCE;
//enum的构造函数只支持private 而且因为所有枚举都继承自java.lang.Enum类 因此枚举不能再继承其他类 但是可以实现接口
private Singleton3(){
}
public void run(){
System.out.println("单例运行");
}
}
public class Main {
public static void main(String[] args) {
Singleton1.getInstance().run();
Singleton2.getInstance().run();
Singleton3.INSTANCE.run();
}
}
我们分别使用饿加载、线程安全的懒加载、枚举来实现单例,都是线程安全的。
五、原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,
它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
代码例子,对MyClass对象的克隆。Java中clone()已经是Object类的方法,想要重写clone()只需要实现Cloneable这个标记接口,但是注意深拷贝和浅拷贝,这里只是谈论设计模式思想,由于篇幅问题不讨论这个问题了:
package 原型模式;
class MyClass implements Cloneable {
private int attr1;
private int attr2;
public MyClass(int attr1,int attr2){
this.attr1 = attr1;
this.attr2 = attr2;
}
@Override
public MyClass clone(){
MyClass obj = null;
try {
obj = (MyClass)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString(){
return "attr1:"+attr1+" attr2:"+attr2;
}
}
public class Main {
public static void main(String[] args) {
MyClass mc1 = new MyClass(1,2);
MyClass mc2 = mc1.clone();
System.out.println(mc2);//attr1:1 attr2:2
}
}
通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。
结构类的设计模式
六、适配器模式
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
package 适配器模式;
interface Interface1 {
void method1();
void method2();
}
class Class1 {
public void method1(){
System.out.println("this is method1");
}
}
//Class2就是一个Class1和Interface1之间的适配器 这种模式是类适配器模式
class Class2 extends Class1 implements Interface1 {
//method1通过继承得到了 重写method2即可
@Override
public void method2() {
System.out.println("this is method2");
}
}
//Class3也是一个Class1和Interface1之间的适配器 只不过是通过Class1的实例进行了method1的适配 叫对象适配器模式
class Class3 implements Interface1 {
private static final Class1 proxy = new Class1();
@Override
public void method1() {
proxy.method1();
}
@Override
public void method2() {
System.out.println("this is method2");
}
}
public class Main {
public static void main(String[] args) {
Class2 obj1 = new Class2();
obj1.method1();
obj1.method2();
Class3 obj2 = new Class3();
obj2.method1();
obj2.method2();
}
}
适配方法有两种,类适配和对象适配,类适配是通过继承类A再实现接口B来实现A和B之间的适配;而对象适配是通过实现接口B,然后使用A对象作为成员变量进行代理实现A和B的适配。
优点:
通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
复用了现存的类,解决了现存类和复用环境要求不一致的问题。
将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,
同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点:
对于对象适配器来说,更换适配器的实现过程比较复杂。
七、桥接模式
将抽象与实现解耦,使得两者可以独立地变化。
代码例子,设想现在我们需要红色直线、绿色方框、黑色三角形、红色方框、红色三角形....如果我们按照传统的设计思路,相当于一个颜色的形状对应了一个类,如果有3种颜色3种形状就是9个类。如果我们使用桥梁模式,将形状和颜色相分离,使用继承/组合的方式对应一个类,就只需要3种颜色类和3种形状类即可:
package 桥梁模式;
interface Printer {
void print();
}
class BlackPrinter implements Printer {
@Override
public void print() {
System.out.println("BlackPrinter is printing...");
}
}
class RedPrinter implements Printer {
@Override
public void print() {
System.out.println("RedPrinter is printing...");
}
}
class GreenPrinter implements Printer {
@Override
public void print() {
System.out.println("GreenPrinter is printing...");
}
}
abstract class Shape {
protected Printer printer;
Shape(Printer printer){
this.printer = printer;
}
public void draw(){
printer.print();
}
}
class Line extends Shape {
private int distance;
Line(Printer printer) {
super(printer);
}
Line(Printer printer,int distance) {
super(printer);
this.distance = distance;
}
@Override
public void draw(){
printer.print();
System.out.println("The "+distance+" distance line has been drawed");
}
}
class Triangle extends Shape {
private int a,b,c;
Triangle(Printer printer) {
super(printer);
}
Triangle(Printer printer,int a,int b,int c) {
super(printer);
this.a = a;
this.b = b;
this.c = c;
}
@Override
public void draw(){
printer.print();
System.out.println("The "+a+" "+b+" "+c+" triangle has been drawed");
}
}
class Square extends Shape {
private int a;
Square(Printer printer) {
super(printer);
}
Square(Printer printer,int a) {
super(printer);
this.a = a;
}
@Override
public void draw(){
printer.print();
System.out.println("The "+a+" square has been drawed");
}
}
public class Main {
public static void main(String[] args) {
//红方形获取
Printer redPrinter = new RedPrinter();
Shape redSquare = new Square(redPrinter,1);
redSquare.draw();//RedPrinter is printing...
// The 1 square has been drawed
//黑直线获取
Printer blackPrinter = new BlackPrinter();
Shape blackLine = new Line(blackPrinter,1);
blackLine.draw();//BlackPrinter is printing...
// The 1 distance line has been drawed
//绿三角形获取
Printer greenPrinter = new GreenPrinter();
Shape greenTriangle = new Triangle(greenPrinter,1,1,1);
greenTriangle.draw();//GreenPrinter is printing...
// The 1 1 1 triangle has been drawed
}
}
上面的代码就是利用组合实现了桥接,减少了类的个数。
桥接模式的使用场景:
1、当一个对象有多个变化因素的时候,通过抽象这些变化因素,将依赖具体实现,修改为依赖抽象。2、当某个变化因素在多个对象中共享时。我们可以抽象出这个变化因素,然后实现这些不同的变化因素。
3、当我们期望一个对象的多个变化因素可以动态的变化,而且不影响客户的程序的使用时。
八、装饰者模式
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更加灵活。
优点:
1、对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
2、可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
3、可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
缺点:
1、使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
2、装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
装饰者模式有以下角色:
● Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
● ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
● Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
● ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
代码例子,设想我们需要一个人,搭配各种而样的衣服,如果每个穿着搭配都需要创建一个类必然会有很多的类,这里我们就可以使用装饰者模式。将人作为被装饰的类,而各种各样的衣服作为装饰者:
package 装饰者模式;
//被装饰对象基类
abstract class Person {
protected String description = "null";
public String getDescription(){
return description;
}
public abstract double cost();
}
//具体被装饰对象
class Man extends Person {
public Man(){
description = "Shopping List:";
}
@Override
public double cost() {
// cost nothing
return 0;
}
}
//装饰者抽象类
abstract class HatDecorator extends Person {
protected Person person;
public HatDecorator(Person person){
this.person = person;
}
}
abstract class ShoesDecorator extends Person {
protected Person person;
public ShoesDecorator(Person person){
this.person = person;
}
}
//具体的装饰者
class GreenHatDecorator extends HatDecorator {
public GreenHatDecorator(Person person) {
super(person);
}
@Override
public double cost() {
return 90+person.cost();
}
@Override
public String getDescription() {
return person.getDescription()+" a greent hat";
}
}
class BlackShoesDecorator extends ShoesDecorator {
public BlackShoesDecorator(Person person) {
super(person);
}
@Override
public double cost() {
return 80+person.cost();
}
@Override
public String getDescription() {
return person.getDescription()+" black shoes";
}
}
public class Main {
public static void main(String[] args) {
Person person = new Man();
person = new GreenHatDecorator(person);//戴上绿帽
System.out.println(person.getDescription()+"¥"+person.cost());Shopping List: a greent hat¥90.0
person = new BlackShoesDecorator(person);//穿上黑鞋
System.out.println(person.getDescription()+"¥"+person.cost());Shopping List: a greent hat black shoes¥170.0
}
}
以上代码Person类作为基类,Man类作为装饰者,HatDecorator和ShoesDecorator作为抽象装饰者,延伸出具体装饰者。使得Person类实例的装饰可以添加,而且不需要生成大量的类的个数。
九、组合模式
将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和使用具有一致性。
代码例子,存在一些Composite存储Leaf,Leaf则不能再被存储:
package 组合模式;
import java.util.LinkedList;
abstract class Component {
public void display(){
System.out.println("Not able to display");
}
public void remove(){
System.out.println("Not able to remove");
}
}
class Leaf extends Component {
private String name;
public Leaf(String name){
this.name = name;
}
@Override
public void display(){
System.out.print(name+" ");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Leaf other = (Leaf) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
class Composite extends Component {
private LinkedList<Leaf> leafList;
public Composite(LinkedList<Leaf> leafList){
this.leafList = leafList;
}
@Override
public void display(){
for(int i = 0; i<leafList.size(); i++){
leafList.get(i).display();
}
System.out.println();
}
@Override
public void remove(){
leafList.clear();
leafList = null;
}
public boolean remove(Leaf leaf){
return leafList.remove(leaf);
}
}
public class Main {
public static void main(String[] args) {
LinkedList<Leaf> leafList = new LinkedList<Leaf>();
leafList.add(new Leaf("leaf1"));
leafList.add(new Leaf("leaf2"));
leafList.add(new Leaf("leaf3"));
Composite composite = new Composite(leafList);
composite.display();//leaf1 leaf2 leaf3
composite.remove(new Leaf("leaf2"));
composite.display();//leaf1 leaf2
}
}
当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。
十、外观(门面)模式
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。简单来说就是把子系统中常用的模块放到门面类(可以有多个)中,客户端访问子系统直接访问门面类就好,相当于一个小区的门卫。
代码例子,我们有三个模块,使用一个门面类包装这三个模块:
package 外观模式;
class ModelA {
public void methodA(){
System.out.println("This is methodA");
}
}
class ModelB {
public void methodB(){
System.out.println("This is methodB");
}
}
class ModelC {
public void methodC(){
System.out.println("This is methodC");
}
}
class Facade {
private ModelA a = new ModelA();
private ModelB b = new ModelB();
private ModelC c = new ModelC();
public void MethodA(){
a.methodA();
}
public void MethodB(){
b.methodB();
}
public void MethodC(){
c.methodC();
}
}
public class Main {
public static void main(String[] args) {
Facade facade = new Facade();//获取门面对象
facade.MethodA();//使用门面对象
facade.MethodB();
facade.MethodC();
}
}
适用环境:在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。
在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
优点:实现了子系统与客户端之间的松耦合关系。
客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
十一、享元模式
在享元模式中可以共享的相同内容称为 内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为 外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。
享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
代码例子,我们使用一个字符串作为内蕴条件,另一个字符串作为外蕴条件,使用享元模式:
package 享元模式;
import java.util.HashMap;
import java.util.Map;
//抽象享元角色类
interface FlyWeight {
void operation(String outerState);
}
//具体享元角色类 存在一个内蕴条件 传入外蕴条件
class ConcreteFlyWeight implements FlyWeight {
//内蕴状态
private String intrinsicState;
public ConcreteFlyWeight(String state) {
this.intrinsicState = state;
}
@Override
public void operation(String outerState) {
//处理内蕴条件
System.out.println("内蕴条件为:"+intrinsicState);
//处理外蕴条件
System.out.println("外蕴条件为:"+outerState);
}
}
//享元工厂角色类 客户端不可以直接将享元类实例化,而必须通过一个工厂对象获取
class FlyWeightFactory {
//内蕴状态和享元对象的映射
private Map<String,FlyWeight> cache = new HashMap<String,FlyWeight>();
public FlyWeight getFlyWeight(String intrinsicState){
FlyWeight flyWeight = cache.get(intrinsicState);
if(flyWeight == null){
//不存在则创建一个新的 然后放入缓存
flyWeight = new ConcreteFlyWeight(intrinsicState);
cache.put(intrinsicState, flyWeight);
}
return flyWeight;
}
}
public class Main {
public static void main(String[] args) {
FlyWeightFactory factory = new FlyWeightFactory();
FlyWeight flyWeight1 = factory.getFlyWeight("String1");
factory.getFlyWeight("String2").operation("外蕴条件");//内蕴条件为:String2 外蕴条件为:外蕴条件
FlyWeight flyWeight3 = factory.getFlyWeight("String1");
System.out.println(flyWeight1==flyWeight3);//true
}
}
可以看到相同的内蕴条件下是共享对象的,而不会新建一个对象,我们可以在内蕴条件相同的情况下加上外蕴条件,实现内蕴条件相同的对象共享,而外蕴条件自己添加。本质就是将对象分为相同的部分和不同的部分,多个想要获得这个对象的地方共享这个对象的相同部分,添加上这个对象的不同部分构成自己所需要的对象。实现了享元。
优点:
1、享元模式的优点在于它能够极大的减少系统中对象的个数。
2、享元模式由于使用了外蕴状态,外蕴状态相对独立,不会影响到内蕴状态,
所以享元模式使得享元对象能够在不同的环境被共享。
缺点
1、由于享元模式需要区分外蕴状态和内蕴状态,使得应用程序在某种程度上来说更加复杂化了。
2、为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
代理模式参考自https://www.cnblogs.com/cenyu/p/6289209.html,做出了一些整理和删减
十二、代理模式
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用
另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式可以分为静态代理、动态代理、Cglib子类代理。
1、静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
代码例子,模拟数据库连接后的保存动作:
package 静态代理.静态代理;
//接口
interface UserDao {
void save();
}
//目标对象
class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("已保存数据");
}
}
//代理对象
class UserDaoProxy implements UserDao {
private UserDao target;
public UserDaoProxy(UserDaoImpl target) {
this.target = target;
}
@Override
public void save() {
//模拟存储之前的活动
System.out.println("保存数据之前做的操作...");
target.save();
//模拟存储之后的活动
System.out.println("保存数据之后做的操作...");
}
}
public class Main {
public static void main(String[] args) {
//目标对象
UserDaoImpl target = new UserDaoImpl();
//代理对象 把目标对象传递给代理对象 建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
//执行代理方法
proxy.save();//保存数据之前做的操作... 已保存数据 保存数据之后做的操作...
}
}
优点:
- 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式。
2、动态代理
动态代理的特点:
①、代理对象不需要实现接口
②、代理对象的生成利用JDK的API,动态再内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口)
③、动态代理也叫做JDK代理、接口代理
只需要使用Proxy类下的newProxyInstance方法,参数有三个,分别是ClassLoader loader,Class<?>[] interfaces,InvocationHandler h,分别代表指定类加载器、目标对象实现的接口类型、事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入,这个参数需要实现InvocationHandler。
代码例子,模拟数据库的保存:
package 代理模式.动态代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//接口
interface UserDao {
void save(int num);
}
//目标对象
class UserDaoImpl implements UserDao {
@Override
public void save(int num){
System.out.println("已保存数据"+num);
}
}
//代理对象工厂 获取代理对象 不需要实现接口 但是需要指定接口类型
class ProxyFactory {
//一个代理对象工厂维护一个目标对象 生产代理对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//给目标对象生产代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//保存数据之前的操作
System.out.println("保存数据之前做的操作...");
Object result = method.invoke(target, args);
System.out.println("保存数据之后做的操作...");
return result;
}
});
}
}
public class Main {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDaoImpl();
//这是目标对象
System.out.println(target.getClass());//class 代理模式.动态代理.UserDaoImpl
//给定目标对象 创建代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());//class 代理模式.动态代理.$Proxy0
//通过代理对象执行方法
proxy.save(5);
//保存数据之前做的操作...
//已保存数据5
//保存数据之后做的操作...
}
}
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
3、Cglib代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。
①、Cglib需要引入Cglib的jar包,但是Spring核心包已经包含了Cglib功能,所以直接引入spring.jar即可
②、引入功能包后就可以在内存中动态构建子类。
③、代理的类不能为final,不然会报错。
④、目标对象的方法如果为final/static,那么就不会被拦截,也就是不会执行目标对象额外的业务方法。
⑤、这种代理可以允许目标对象没有实现接口。
代码例子,还是我们的数据库存储例子:
package 代理模式.Cglib代理;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class UserDao {
public void save(int num){
System.out.println("已保存数据"+num);
}
}
//Cglib子类代理工厂 对UserDao在内存中动态创建一个子类对象
class ProxyFactory implements MethodInterceptor {
//目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
//1、工具类
//Enhancer允许为非接口类型创建一个Java代理。
//Enhancer动态创建了给定类型的子类但是拦截了所有的方法。和Proxy不一样的是,不管是接口还是类他都能正常工作。
Enhancer en = new Enhancer();
//2、设置父类
en.setSuperclass(target.getClass());
//3、设置回调函数
en.setCallback(this);
//4、创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("保存数据之前做的操作...");
Object result = method.invoke(target, args);
System.out.println("保存数据之后做的操作...");
return result;
}
}
public class Main {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
proxy.save(4);
}
}
这是在字节码层面新生成的一个对象做的代理,效率自然比反射高。
如果加入容器的目标对象有实现接口,用JDK代理(动态代理)就好,如果没有实现接口,用Cglib代理。
优点:
1、代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。
2、代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。
代理对象也可以对目标对象调用之前进行其他操作。
缺点:
1、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
2、增加了系统的复杂度。
后面的第13-23种设计模式见我的另一篇文章
http://blog.youkuaiyun.com/qq_35580883/article/details/79327771