Java 23 种设计模式学习

Posted 皮豪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 23 种设计模式学习相关的知识,希望对你有一定的参考价值。

Java设计模式

解决普遍存在的问题,反复出现的各种问题,所提出的解决方案。

设计模式七大原则

  • 设计模式七大原则:
  1. 单一职责
  2. 接口隔离
  3. 依赖倒转
  4. 里氏替换
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则

面向对象 => 功能模块[设计模式+算法] => 框架[调用多种模式] => 架构[服务器集群]

单一职责原则

  1. 降低类的复杂度,一个类只负责一项职责
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,应当遵守单一职责原则,保有逻辑足够简单,才可以在代码级违法单一职责原则,只有类中方法数量足够少,可以在方法级别保持单一职责原则
// 方法层面的单一职责
class Vehicle
	
	public void runRoad() 
		System.out.println("公路");
	
	public void runRiver() 
		System.out.println("水路");
	
	public void runAirLine() 
		System.out.println("航道");
	

// 类层面的单一职责
class RoadVehicle
	public void run() 
		System.out.println("公路");
	


class RiverVehicle
	public void run() 
		System.out.println("水路");
	


class AirVechile
	public void run() 
		System.out.println("航道");
	

接口隔离原则

将一个接口的功能进行拆分,让其成模块化的。这里的例子就是,一个接口有五个方法,如果两个类继承了,此接口,则需要实现十次。有些方法是用不到的。所以需要将折口拆分成几个接口。

这样的使用的和维护的时候,更有针对性,而且耦合度更低。当然,其缺点就是可能会造成模块粒度不可控。

依赖倒转原则

将一个类型抽象为接口,这类型都是相同的属性,具有不同的细节特性。
如果动物 下 有 狗和猫,其都有走路和吃食的 属性(方法),但吃的不一样,所以需要不同的实现。

但是整体业务是一样的,只需要更换不同的实现。面向接口编程就如同面向规范编程,不同产商同一标准,生产相同的零件,可能是颜色不一样,Logo不一样。

下面只是 依赖关系 传递的一种操作。

  1. 接口传递
  2. 构造方法传递
  3. setter传递
public class DependencyInversion 
	public static void main(String[] args) 
		User user = new User();
		user.receive(new EmailMessage());
		user.receive(new WeChatMessage());
	


class User 
	public void receive(Message msg) 
		System.out.println(msg.getInfo());
	


interface Message
	String getInfo();


class EmailMessage implements Message

	@Override
	public String getInfo() 
		// TODO Auto-generated method stub
		return "电子邮件";
	
	


class WeChatMessage implements Message

	@Override
	public String getInfo() 
		// TODO Auto-generated method stub
		return "微信信息";
	
	

总结:

  1. 底层最好是抽象类或接口,程序稳定性更好
  2. 变量的声明类型尽量是抽象类和接口,这样在使用时,就是可以利用多态的特性,而不是直接把接口写死,利于后期代码的更新和维护。
  3. 继承时遵循晨氏替换原则

里氏替换原则

这个我的总结就是不应该破坏原有的封装。在添加新的功能时,应当考虑原有代码的这个封装和可能存在的误导性。新的类或者方法需要与之前的模块进行一定的区分和隔离。

这个隔离,就是把原有的继承关系,变成依赖关系。即可以重写方法,又可以使用相关性非常高的原有的继承关系。

class Base
	public void Fly() 
		System.out.println("BASE");
	


class A extends Base 
	public void Fly() 
		System.out.println("A");
	


class B extends Base
	private A a;
	
	public void setA(A a) 
		this.a = a;
	
	
	public void Fly() 
		System.out.println("B");
	
	
	public void AFly() 
		this.a.Fly();
	
	
	public A getA() 
		return a;
	

开闭原则

这原则的核心。

很像是依整倒转原则,不过基类变成了抽象类。

主要是提供使用接口,让其不需要为每个使用到的类而重写一个功能方法,直接使用抽象类的方法就可以了。


public class Ocp 
	public static void main(String[] args) 
		Panel p = new Panel();
		p.drawGraph(new Triangle());
	

class Panel 
	public void drawGraph(Shape shape) 
		shape.draw();
	

abstract class Shape 
	public abstract void draw();

class Triangle extends Shape 
	@Override
	public void draw() 
		System.out.println("画三角形");
	


class Retangle extends Shape 
	@Override
	public void draw() 
		System.out.println("画矩形");
	

迪米特法则

又称最少知道原则。

简单理解为,如果不依赖,就不要在本类创建其任何相关的局部变量。

不依赖的意思就是不是直接朋友,不是直接朋友就是不依赖。

老师内,有直接依赖学生,所以老师可以在局部创建学生的变量,而学生并不依赖老师,所以在使用的时候,不能直接创建老师的局部变量。

所以这种操作,可以转为 其他的直接朋友进行操作,就是一个主抽象类,即依赖了老师,又依赖了学生,那么这种局部的操作,可以转给此类进行操作。

  1. 迪米特法则的核心是降低类之间的耦合
  2. 由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间耦合关系,并不是要求公开赛没有依赖关系。
class Manager
	public void getAllTeacher() 
	

	public void getAllStudent() 
	


class Teacher
	List<Student>  myClass;
	
	public void showMyClass(Manager manager) 
		myClass = new ArrayList<Student>();
		manager.getAllStudent();
	


class Student
	
	public void getMyTeachers(Manager manage) 
		manage.getAllTeacher();
	

合成复用原则

原则是尽量使用全成/聚合的方式,而不是使用继承。

就是说,新手可能在使用一个类的时候会直接使用继承,这样显然是主动耦合了,在维护上会出现很大的关联性的问题。一般在使用一个类的时候,一般使用 依赖,合成 和 聚合。

总结

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 为了交互对象之间的松耦合设计而努力。

设计模式

类型

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  3. 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)

创建型模式

单例模式

懒汉式(线程安全,同步方法)

  1. 解决了线程不安全问题,但效率太低了,也不推荐,但我记录下。
public class SongletonSecureThread 
	
	private static SongletonSecureThread sst;
	
	private SongletonSecureThread() 
	
	
	public static synchronized SongletonSecureThread getInstance() 
		if(sst == null) 
			sst = new SongletonSecureThread();
		
		return sst;
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		System.out.println(SongletonSecureThread.getInstance().hashCode());
		System.out.println(SongletonSecureThread.getInstance().hashCode());
	

双重检查

  1. DoubleCheck概念是多线程使用的
  2. 再次判断,线程安全,延迟加载,效率较高
    实际开发中,非常推荐
public class SongletonSecureThread 
	
	private static volatile SongletonSecureThread sst;
	
	private SongletonSecureThread() 
	
	
	public static synchronized SongletonSecureThread getInstance() 
		if(sst == null) 
			synchronized(SongletonSecureThread.class) 
				if(sst == null) 
					sst = new SongletonSecureThread();
				
			
		
		return sst;
	

	public static void main(String[] args) 
		// TODO Auto-generated method stub
		System.out.println(SongletonSecureThread.getInstance().hashCode());
		System.out.println(SongletonSecureThread.getInstance().hashCode());
	

静态内部类

使用了JVM的装载机制,静态内部类在父类装载时并不被实例化,而是在需要实例化时,调用 GetInstance方法,才会装载SIngletonInstance类。

优点:使用JVM的机制实现了懒加载,并且是线程安全的。
推荐使用

public class SingletonInterClass 

	public SingletonInterClass() 
		
	
	
	private static class SingletonInstance
		private static final SingletonInterClass INSTANCE = new SingletonInterClass();
	
	
	public static SingletonInterClass getInstance() 
		return SingletonInstance.INSTANCE;
	

单例枚举

能避免多线程同步的总理 ,还能防止反序列化重新创建新的对象
《effective Java》作者推荐

推荐使用

public class SingletonEnum 
	public static void main(String[] args) 
		System.out.println(Singleton.INSTANCE.get() == Singleton.INSTANCE.get());
	



enum Singleton
	INSTANCE;
	private SingletonEnum se;

	Singleton() 
		if(se == null) 
			se =new SingletonEnum();
		
	
	
	public SingletonEnum get() 
		return se;
	
	

总结

单例模式注意事项和细节说明:

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

Runtime 使用的是 饿汉式 单列模式的编写方法。

每次使用的时候都需要考虑使用场景。

工厂模式

通过一个工厂类,获取不通的解决方案。

工厂可以是类也可以是方法。

最好的例子,就是造包子,不同的馅料。

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类。

public class BunFactory 
	
	public Bun createBun(String bunType) 
		Bun bun = null;
		switch(bunType) 
		case "hnrb": 
			bun = new HuanMeetBun();
			break;
		case "hncb": bun = new HunanGreensBun(); break; 
		case "sdcb": bun = new SdGreendsBun(); break;
		case "sdrb": bun = new SdMeetBun();break;
		

		return bun;
	
	
	public static void main(String[] args) 
		BunFactory bunfactory = new BunFactory();
		bunfactory.createBun("hncb");
		bunfactory.createBun("hnrb");
		bunfactory.createBun("sdcb");
	

抽象工厂方法

虽然这个抽象方法也就是一个接口,但其是用了几个工厂类的实现。
就是进一步的将工厂进行抽象了,先是基于工厂为基础进行开发。而抽象工厂,则是直接将这个抽象工厂作为一个中心。(这个图中的UML是错的)

静态工厂

在一个里有一个静态方法用来将输入的数字yyyyMMdd转成LocalDate

public class LocalDateFactory 

	public static LocalDate fromInt(int yyyyMMdd) 
		int year = yyyyMMdd / 10000;
		int month = yyyyMMdd % 10000 / 100;
		int day = yyyyMMdd % 100;
		if (month > 12 && day > 31) 
			throw new RuntimeException("number out of the normal range");
		
		System.out.println(year + " " + month + " " + day);

		return LocalDate.of(year, month, day);
	

	@Test
	public void testMain() 
		System.out.println(LocalDateFactory.fromInt(20020321));
	

抽象工厂

这里的抽象工厂的作用就是将使用的工厂和接口都抽象出来,然后每种类型都需要不同的实现。

测试 方法

@Test
public void testFactory() throws IOException 
	FastFactory ff = new FastFactory();
	
	HtmlDocument hd = ff.createHtml("# Hello World \\n");
	hd.save(Paths.get(".","good.html"));

工厂类接口和工厂实现类

public abstract class AbstractFactory 
	
	public abstract HtmlDocument createHtml(String md);
	
	public abstract WordDocument createWord(String md);


// -----------

public class FastFactory extends AbstractFactory

	@Override
	public HtmlDocument createHtml(String md) 
		return new FastHtmlDocument(md);
	

	@Override
	public WordDocument createWord(String md) 
		// TODO Auto-generated method stub
		return null;
	
	

Document接口和实现类


public interface HtmlDocument 

	public String toHtml();

	public void save(Path path) throws IOException;


// -------------------------------
public class FastHtmlDocument implements HtmlDocument
	private String md;

	
	public FastHtmlDocument(String md) 
		this.md = md;
	

	@Override
	public String toHtml() 
		// TODO Auto-generated method stub
		return md.lines().map(s -> 
			if(s.startsWith("#"))
				return "<h1>"+s.substring(1)+"</h1>";
			return "<p>"+s+"</p>";
		).reduce("", (acc,s)-> acc+ s + "\\n");
	

	@Override
	public void save(Path path) throws IOException 
		Files.write(path,toHtml().getBytes("UTF-8"));
	

工厂模式小结

  1. 工厂模式的意义
    将实例化的对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性
  2. 三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
  3. 设计模式的依赖抽象原则

设计模式的依赖抽象原则:

  • 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回。有的书推荐,变量不要直接持有具体的引用。
  • 不要让类继承具体类,而是继承抽象类或者是实现interface。
  • 不要覆盖蕨类中已经实现的方法

原型模式-基本介绍

基本介绍

  1. 原型模式(Prototype)是指:用原型实例指定创建对象的各类,并且通过挂账这些原型,创建新的对象。
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过 请求原型对象拷贝它们自己来实施创建,即 对象.clone()
  4. 需要实现Cloneable接口,不然会报错
public class Sheep implements  Cloneable 
	
	private String name;
	private int age;
	public String getName() 
		return name;
	
	public void setName(String name) 
		this.name = name;
	
	public int getAge() 
		return age;
	
	public void setAge(int age) 
		this.age = age;
	
	
	
	public Sheep(String name, int age) 
		super();
		this.name = name;
		this.age = age;
	
	@Override
	public String toString() 
		return "Sheep [name=" + name + ", age=" + age + "]";
	

	@Override
	protected Object clone() 
		// TODO Auto-generated method stub

		Sheep sheep =null;
		try 
			sheep = (Sheep) super.clone();
		 catch (CloneNotSupportedException e) 
			e.printStackTrace();
		

		return sheep;
	
	
	


public class Test 

	public static void main(String[] args) throws CloneNotSupportedException 
		System.out.println(System.getProperty("java.version"));
		Sheep tomk = new Sheep("我是Tomk",10);
		System.out.println(tomk);
		Sheep tomb = (Sheep)tomk.clone();
		tomb.setName("我是Tomb");
		tomb.setAge(100);
		System.out.println(tomb);
	

Spring的原型的应用

Spring配置Bean时可以选择使用原型还是单例。如果选择原型,则每次创建的对象都不同。

  • 分析Spring的bean创建的代码
    <bean id="helloWorld" class="com.example.HelloWorld" scope="prototype">
        <property name="message" value="Hello World!"/>
    </bean>

深拷贝

浅拷贝时,对象内的成员属性如果是引用类型,则直接复制其引用的地址,而不是重新拷贝一份。这样的问题就是所有浅拷贝的对象,都是同一个引用成员属性。

	private String name;
	private int age;
	private Sheep friend;
clone方法里手动拷贝
  • 引用对象越多越不方便
public class SheepDeep implements Cloneable 

	private String name;
	private int age;
	private SheepTarget sheepTarget;

	@Override
	public String toString() 
		return "Sheep [name=" + name + ", age=" + age + ", sheepTarget=" + sheepTarget + "]";
	

	public SheepTarget getSheepTarget() 
		return sheepTarget;
	

	public void setSheepTarget(SheepTarget sheepTarget) 
		this.sheepTarget = sheepTarget;
	

	public String getName() 
		return name;
	

	public void setName(String name) 
		this.name = name;
	

	public int getAge() 
		return age;
	

	public void setAge(int age) 
		this.age = age;
	

	public SheepDeep(String name, int age, SheepTarget sheepTarget) 
		super();
		this.name = name;
		this.age = age;
		this.sheepTarget = sheepTarget;
	

	@Override
	protected Object clone() throws CloneNotSupportedException 
		SheepDeep sheep = (SheepDeep) super.clone();
		// 使用这种方法是这样子的,需要将每个引用类型的成员属性都进行克隆
		sheep.setSheepTarget((SheepTarget) sheepTarget.clone());

		return sheep;
	

使用序列化来实现拷贝(推荐)

原理: 将对象序列化成字节数组,然后将字节数组反序列化。这样原来的值都需要创建新的内存空间,所以引用会发生改变。以达到深度拷贝的效果。

public class SheepDeep implements Cloneable ,Serializable

	private String name;
	private int age;
	private SheepTarget sheepTarget;

	@Override
	public String toString() 
		return "Sheep [name=" + name + ", age=" + age + ", sheepTarget=" + sheepTarget + "]";
	

	public SheepTarget getSheepTarget() 
		return sheepTarget;
	

	public void setSheepTarget(SheepTarget sheepTarget) 
		this.sheepTarget = sheepTarget;
	

	public String getName() 
		return name;
	

	public void setName(String name) 
		this.name = name;
	

	public int getAge() 
		return age;
	

	public void setAge(int age) 
		this.age = age;
	

	public SheepDeep(String name, int age, SheepTarget sheepTarget) 
		super();
		this.name = name;
		this.age = age;
		this.sheepTarget = sheepTarget;
	

	@Override
	protected Object clone() throws CloneNotSupportedException 
		SheepDeep sheep = (SheepDeep) super.clone();
		// 使用这种方法是这样子的,需要将每个引用类型的成员属性都进行克隆
		sheep.setSheepTarget((SheepTarget) sheepTarget.clone());
		return sheep;
	
	
	public Object deepClone() 
		// 现实中使用这里应该是需要使用工具类的
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream ois = null;
		try 
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			
			// 把对象和对象的值全部序列化到内存
			oos.writeObject(this);
			
			// 反序列化,把字节转成对象,会重新为成员属性创建新的对象
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			return ois.readObject();
		 catch (Exception e) 
			// TODO: handle exception
			e.printStackTrace();
		
		return null;

	

原型模式总结

默认是使用浅拷贝,如果需要深度拷贝,则需要自定义方法,会破坏OCP原则。

原型的话,就是在Bean的 创建的时候使用,我想,如果创建一个容器,或者获取一些临时的Bean的时候,都需要用到。

建造者模式

好理解,易操作。
设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好,也就是说,这种设计方案,把产品和创建产品的过程封装在一起,耦合性增加了。

解决方案:将产品和产品建造过程解耦 =》 建造者模式。

  • 尚硅谷的版本

  • ChatGPT写的版本

public class Burger 
    private String bun;
    private String patty;
    private boolean cheese;

    private Burger(BurgerBuilder builder) 
        this.bun = builder.bun;
        this.patty = builder.patty;
        this.cheese = builder.cheese;
    

    public static class BurgerBuilder 
        private String bun;
        private String patty;
        private boolean cheese;

        public BurgerBuilder setBun(String bun) 
            this.bun = bun;
            return this;
        

        public BurgerBuilder setPatty(String patty) 
            this.patty = patty;
            return this;
        

        public BurgerBuilder setCheese(boolean cheese) 
            this.cheese = cheese;
            return this;
        

        public Burger build() 
            return new Burger(this);
        
    

    // Getters and setters


// Example usage:
Burger burger = new Burger.BurgerBuilder()
        .setBun("Sesame Seed")
        .setPatty("Beef")
        .setCheese(true)
        .build();

生成器模式

聚合一些成员属性,然后使用这些成员属性建构一个新的对象。
下面的例子就是使用 使用一些字符串,建构一个URL链接。


public class URLBuilder 
	
	public static URL builder() 
		return new URL();
	
	
	@Test
	public void testBuilder() 
		String url = URLBuilder.builder()
		.setDomain("www.kbug.cn")
		.setPath("/")
		.setQuery(Map.of("name","mark"))
		.setScheme("https")
		.build();
		System.out.println("URL=> "+url);
	



public class URL 
	private String domain;
	private String scheme;
	private String path;
	private Map<String,Object> query;
	public String getDomain() 
		return domain;
	
	public URL setDomain(String domain) 
		this.domain = domain;
		return this;
	
	public String getScheme() 
		return scheme;
	
	public URL setScheme(String scheme) 
		this.scheme = scheme;
		return this;
	
	public String getPath() 
		return path;
	
	public URL setPath(String path) 
		this.path = path;
		return this;
	
	public Map<String, Object> getQuery() 
		return query;
	
	public URL setQuery(Map<String, Object> query) 
		this.query = query;
		return this;
	
	
	@Override
	public String toString() 
		return "URL [domain=" + domain + ", scheme=" + scheme + ", path=" + path + ", query=" + query + "]";
	
	
	public String mapToQueryParam(Map<String,Object> map) 
		StringBuilder sb = new StringBuilder();
		sb.append("?");
		
		for(String key: map.keySet()) 
			sb.append(key+"="+map.get(key));
			sb.append("&");
		
		String queryStr = sb.toString();
		queryStr = queryStr.substring(0,queryStr.length()-1);
		return queryStr;
	
	
	public String build() 
		return scheme+"://"+domain+path+mapToQueryParam(query);
	

	

接口适配器

我觉得在适配器里把聚合的思想应用 的非常好。就是把一些不能直接使用的资源,用适配器过滤一下就能用了。并且耦合性不高,扩展性很好。

(视频确实讲的很全)

然后就是有几种实现方式:

public interface BigInter 
	
	public void m1();
	public void m2();
	public void m3();
	public void m4();


public abstract class RanAbsAdapter implements BigInter

	@Override
	public void m1() 
		// TODO Auto-generated method stub
		
	

	@Override
	public void m2() 
		// TODO Auto-generated method stub
		
	

	@Override
	public void m3() 
		// TODO Auto-generated method stub
		
	

	@Override
	public void m4() 
		// TODO Auto-generated method stub
		
	
	
	


public class TestAbsAdapter 

	
	@Test
	public void testAbsAdapter() 
		BigInter bi =new RanAbsAdapter() 

			@Override
			public void m1() 
				super.m1();
				System.out.println("执行M1");
			
			
		;
		bi.m1();
	

总结

就是依赖倒转,为每个可能实现一个Adapter类,然后需要实现对应的操作。

类适配器:在Adapter里,将src当做类,继承
对象适配器:在Adapter里,将src作为一个对象,持有
接口适配器:以Adapter里,将SRC作为一个接口实现

下图就是接口适配器。

接口模式

  1. 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
  2. 是一种结构型设计模式
  3. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来。从而可以保持各部分的独立性以及应对他们的功能扩展。

具体的操作:就是抽象一个接口,然后聚合这个接口,但是其使用的依赖倒转,其本质的实现是不相同的。

public class TestDridge 

	@Test
	public void testBridge() 
		
		Phone phone = new PatPhone(new HuaWei());
		phone.use();
	


interface Brand
	public String getInfo();
	public void call();


class HuaWei implements Brand

	@Override
	public String getInfo() 
		return "中国华为";
	

	@Override
	public void call() 
		System.out.println("Use Huawei 打电话");
	


class Realme implements Brand 

	@Override
	public String getInfo() 
		return "Oppo 真我";
	

	@Override
	public void call() 
		System.out.println("Fuck up Redmi, I m realme. ");
	
	


abstract class Phone 
	Brand brand;
	
	public Phone(Brand brand) 
		this.brand = brand;
	
	
	public void use() 
		brand.call();
	


class PatPhone extends Phone
	
	public PatPhone(Brand brand) 
		super(brand);
	

装饰者设计模式

装饰者就相当于快递打包,一层一层的。线性的角度来看有点像链表,其实就是一个层层加码的过程。你需要什么,你就需要套着什么。例子就是咖啡加一些小料。

我的感觉就是通过引用一层套着一层,在功能上是可以无限扩充的。就是基于一个主体,无限 扩充。

一个哲学的概念就是:“学习我,成为我”。

  • 装饰者模式:动态的将新功能附加到对象上,在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则。
public class TestDecorator 

	@Test
	public void testDecorator() 
		Drink order = new Latte();
		order = new Paprika(order);
		order = new Salt(order); // 拿铁加盐

		System.out.println(order.cost());
		System.out.println(order.getIntro());
		;
	


abstract class Drink 

	private String intro;
	private double price;

	public String getIntro() 
		return intro;
	

	public void setIntro(String intro) 
		this.intro = intro;
	

	public double getPrice() 
		return price;
	

	public void setPrice(double price) 
		this.price = price;
	

	public abstract double cost();


class Coffee extends Drink 

	@Override
	public double cost() 
		return super.getPrice();
	



class Espresso extends Coffee 

	public Espresso() 
		setPrice(10.99F);
		setIntro("越南浓咖啡");
	


class Latte extends Coffee 

	public Latte() 
		setPrice(10.66F);
		setIntro("湖南打铁 price: " + getPrice());
	


class Decorator extends Drink 

	private Drink drink;

	/**
	 * 这里构造方法就是相当于嵌套,也就是说 装饰模式 <br>
	 * 就是 将对象以引用的方法嵌套在其他对象 中 <br>
	 * 非常像一个链接,但不是一个数据结构,而是代码的抽象逻辑结构
	 * 
	 * @param drink
	 */
	public Decorator(Drink drink) 
		this.drink = drink;
	

	@Override
	public double cost() 
		// TODO Auto-generated method stub
		// 这是不应该是+drink的price,而加drink 的 汇总值
		return getPrice() + drink.cost();
	

	@Override
	public String getIntro() 
		// TODO Auto-generated method stub
		return super.getIntro() + " price: " + getPrice() + " && " + drink.getIntro();
	


// 下面是一些调料,这些调料都会继承装饰器

class Paprika extends Decorator 

	public Paprika(Drink drink) 
		super(drink);
		setPrice(10.0f);
		setIntro("香甜可口湖南辣椒");
	


class Salt extends Decorator 

	public Salt(Drink drink) 
		super(drink);
		setPrice(1.0f);
		setIntro("不咸的盐");
	

下面是执行结果

51.65999984741211
不咸的盐 price: 1.0 && 香甜可口湖南辣椒 price: 10.0 && 香甜可口湖南辣椒 price: 10.0 && 香甜可口湖南辣椒 price: 10.0 && 香甜可口湖南辣椒 price: 10.0 && 湖南打铁 price: 10.65999984741211

这个例子是很容易的理解的,非常类似于递归。需要注意的事,在返回price时,需要返回前几个Price总的值,而不是返回当前的price。可以说是对象嵌着对象的递归操作。

组合模式

这是把系统中所有模块都有统一的特性,然后这些模块都是有相互的关系,上下级。然后组成一套系统。

所以说每个模块都实现了这个Component类,然后又是递进的关系进行依赖。

(我越来越感觉这是不是一种抽象级别的数据结构)

  1. 简化客户端操作。客户端只需要面对一致的对象,而不用考虑整体部分或者节点叶子的问题。
  2. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何发动。
  3. 方便创建出复杂的层次结构,客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
  4. 需要遍历组织机构,或者处理对象具有树形结构时,非常适合使用组合模式
  5. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式。
@Test
public void testComposite() 
	University us = new University("清华大学","让中华震惊世界");
	us.add(new Department("计算机科学与工程学院", ""));
	us.add(new Department("材料学院", ""));
	us.print();



public abstract class OriganizationComponent 
	
	private String name;
	private String intro;
	
	protected void add(OriganizationComponent o) 
		throw new UnsupportedOperationException();
	
	
	protected void remove(OriganizationComponent o) 
		throw new UnsupportedOperationException();
	

	protected void print() 
		throw new UnsupportedOperationException();
	

	public OriganizationComponent(String name, String intro) 
		super();
		this.name = name;
		this.intro = intro;
	

	public String getName() 
		return name;
	

	public void setName(String name) 
		this.name = name;
	

	public String getIntro() 
		return intro;
	

	public void setIntro(String intro) 
		this.intro = intro;
	
	




public class University extends OriganizationComponent

	List<OriganizationComponent> olist = new ArrayList<>();
	
	public University(String name, String intro) 
		super(name, intro);
		// TODO Auto-generated constructor stub
	

	@Override
	protected void add(OriganizationComponent o) 
		// TODO Auto-generated method stub
		olist.add(o);
	

	@Override
	protected void remove(OriganizationComponent o) 
		// TODO Auto-generated method stub
		olist.remove(o);
	

	@Override
	protected void print() 
		System.out.println("=====================");
		System.out.println(getName() + " ------------------ ");
		for(OriganizationComponent o : olist) 
			System.out.println(o.getName());
		
	
	
	



public class Department extends OriganizationComponent 

	List<OriganizationComponent> olist= new ArrayList<>();

	public Department(String name, String intro) 
		super(name, intro);
	

	@Override
	protected void add(OriganizationComponent o) 
		olist.add(o);
	

	@Override
	protected void remove(OriganizationComponent o) 
		olist.remove(o);
	

	@Override
	protected void print() 
		// TODO Auto-generated method stub
		System.out.println("----===----===---===-----");
		System.out.println(getName() +" Department");
		for(OriganizationComponent o: olist) System.out.println(o.getName());
	

总结

跟意思差不多,不过是组合关系。就像人没有心脏是不能活的这种强依赖关系,而形成的组成模式。

外观模式

也是组合的操作。将很多类组合,然后这些类的操作都是统一有条件的操作,是有流程的。

这里的例子非常好理解就是电影院,把各个模块的类,放到电影院,然后只需要操作电影院这个类暴露出来的接口就行了。

让我写个简单的例子。


public class MovieFacade 
	
	PersonelComputer personelComputer;
	Screen screen;
	
	public MovieFacade(PersonelComputer personelComputer, Screen screen) 
		super();
		this.personelComputer = personelComputer;
		this.screen = screen;
	

	public void ready() 
		System.out.println("观影准备");
		personelComputer.on();
		screen.down();
		System.out.println("\\n\\n\\n");
	
	
	public void end() 
		System.out.println(" 观影结束 ");
		personelComputer.off();
		screen.up();
		System.out.println("\\n\\n\\n");
	
	



public class TestMovieFacade 
	
	@Test
	public void testMovieFacade() 
		MovieFacade mf = new MovieFacade(new PersonelComputer(), new Screen());
		mf.ready();
		mf.end();
	

总结

外观模式的注意事项和细节:

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统的复杂性
  2. 外观模式对客户端与子系统的耦合关系,让子系统的内部的模块更易维护和扩展
  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  4. 当系统需求进行分层设计时,可以考虑使用Facade模式
  5. 在维护一个遗留大型系统时,此是可以考虑将难以为扩展的类用一个Facade来实现,让新系统与Facade类交互,提高复用性
  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好,要让系统更有层次,利于维护为目的。

享元模式

就是大家一起共享一些资源,这些资源通常是经常使用,而且模块独立性高。


public class Client 

	@Test
	public void testWebSiteFactory() 
		WebSiteFactory wsf = new WebSiteFactory();

		WebSite wsbd = wsf.getWebsite("百度");
		wsbd.use(new User().setName("Mark"));

		WebSite wstm = wsf.getWebsite("天猫");
		wstm.use(new User().setName("Tony"));

		WebSite wsjk = wsf.getWebsite("天猫");
		wsjk.use(new User().setName("Jerk"));
	



public class ConcreteWebsite extends WebSite 

	private String type = "";

	@Override
	public void use(User user) 
		// TODO Auto-generated method stub
		System.out.println("发布形式为: "+ type+" "+this.hashCode() +" User : "+ user.getName());
	

	public ConcreteWebsite(String type) 
		super();
		this.type = type;
	

	
	



public class User 

	private String name;

	public String getName() 
		return name;
	

	public User setName(String name) 
		this.name = name;
		return this;
	
	
	


public abstract class WebSite 

	public abstract void use(User user);



public class WebSiteFactory 

	private HashMap<String,ConcreteWebsite> pool = new HashMap<>();
	
	public WebSite getWebsite(String type) 
		if(!pool.containsKey(type)) 
			pool.put(type, new ConcreteWebsite(type));
		
		return pool.get(type);
	
	
	public int getWebSiteCount() 
		return pool.size();
	

执行结果如下:

发布形式为: 百度 1466073198 User : Mark
发布形式为: 天猫 398690014 User : Tony
发布形式为: 天猫 398690014 User : Jerk

总结

享元模式有点 像线程池的概念,“享“表示共享,“元”表示对象。

系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。

用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。

享元模式提高了系统的复杂度,需要分享出内部的状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是需要注意的地方

使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制

享元模式经典的应用场景是需要缓冲池的场景,比如String常量池,数据库连接池。

代理模式

不直接使用一个对象,而是间接的使用一个对接,在这个间接的过程中可以添加很多的操作。

静态代理

这里的具体操作就是,将一个具有普遍性的方法,用代理来扩展,如果不具有普遍性,则依然是用其本身的功能。

public class StaticProxy 
	interface ITeacherDao 

		void teach();
	

	static class TeacherDao implements ITeacherDao 

		@Override
		public void teach() 
			System.out.println(" Teacher Dao ");
		
	

	static class TeacherDaoProxy implements ITeacherDao 

		private ITeacherDao teacherDao;

		public TeacherDaoProxy(ITeacherDao teacherDao) 
			this.teacherDao = teacherDao;
		

		@Override
		public void teach() 
			System.out.println("开始自己定义的代理代码 ---- ");
			teacherDao.teach();
			System.out.println("结束代理代码          ----");
		

	



public class TestProxy 
	@Test
	public void testStaticProxy() 
		StaticProxy.TeacherDaoProxy stdp = new StaticProxy.TeacherDaoProxy(new StaticProxy.TeacherDao());
		stdp.teach();
	

动态代理

这个呢,很像Mybatis的操作,就是用动态代理实例化一个接口。

当然这个作用还可以是给一个类加上其他功能,下面的代码中就是给已经的实现的teach再加了一些代码。这类操作可以用来做一个方法的事务管理。如果在动态代理中执行失误了,就回撤数据,如果没有问题就放行。

在尚硅谷这里的学习收获非常大。



public interface ITeacherDao 
	
	void teach();





public class ProxyFactory<T> 

	T target;
	
	public ProxyFactory(T target) 
		this.target = target;
	
	
	
	
	public ProxyFactory() 
	



	public <T> T getProxyInstance()
		if(target == null) 
			throw new RuntimeException("Target 不能为空");
		
		
		Object resultProxy= 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 invokeObject = method.invoke(target, args);

						System.out.println("代理结束   -----");
						return invokeObject;
					
			
		);
		return (T)resultProxy;
	

	
	/**
	 * 这个就有点Mybatis的感觉了
	 * @param <T>
	 * @param targetClass
	 * @return
	 */
	public <T> T getInterfaceProxyInstance(Class targetClass) 

		Class[] clist = targetClass;

		Object resultProxy = Proxy.newProxyInstance(targetClass.getClassLoader(),clist,
				new InvocationHandler() 

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
						System.out.println("代理代码   -----");
						
						System.out.println(" 我直接拿到注解 的代码,进行查询就行");

						System.out.println("代理结束   -----");
						return null;
					
			
		);

		return (T) resultProxy;
	




public class TeacherDao implements ITeacherDao

	@Override
	public void teach() 
		System.out.println(" Teahcer teach the teahced");
	



public class TestDynamicProxy 

	@Test
	public void testDynamicProxy() 
		ITeacherDao teacherDao = new TeacherDao();
		ITeacherDao itdProxy = new ProxyFactory<ITeacherDao>(teacherDao).getProxyInstance();
		itdProxy.teach();
	
	
	@Test
	public void testDynamicInterfaceProxy() 
		ITeacherDao teacherProxy = new ProxyFactory<ITeacherDao>().getInterfaceProxyInstance(ITeacherDao.class);
		teacherProxy.teach();
	

Cglib代理

  1. 静态代理 和 JDK代理模式都要求目标对象是实现一个接口,但有时候对象只是一个单独的对象,并没有实现任何的接口,这个蚨可以使用目标对象子类来实现代理 - 这就是Cglib代理。
  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书将Cglib代理归到动态代理
  3. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java与实现Java接口,它广泛的被许多AOP的框架使用,SpringAOP,实现方法拦截
  4. 在AOP编程中,如何选择代理模式:
    1. 目标对象需要实现接口,用JDK代理
    2. 目标对象不需要实现接口,Cglib代理
  5. Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

下面的代码跑起来需要cblig和asm的库,建议用Maven项目跑,(是这样写的,但我没跑起来,不打算试了。)后面用到的时候肯定能解决问题的。





public class ProxyFactory implements MethodInterceptor

	private Object target;
	
	public ProxyFactory(Object target) 
		this.target = target;
	
	
	public Object getProxyInstance() 
		Enhancer enhancer =new Enhancer();
		// 设置 基类
		enhancer.setSuperclass(target.getClass());
		// 设置 拦截 对象,就是实现拦截方法的对象
		enhancer.setCallback(this);
		return enhancer.create();
	


	@Override
	public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable 
		System.out.println("Cglib 开始了 ~~~ ");
		// TODO Auto-generated method stub
		Object result = method.invoke(target, arg3);
		System.out.println("Cglib 结束了 ~~~ ");
		return result;
	




public class TeacherDao 

	void teach() 
		System.out.println("Cglib Proxy AOP 动态");
	




public class TestCglib 
	
	
	@Test
	public void testCglib() 
		
		
		TeacherDao td = new TeacherDao();
		TeacherDao proxyDao = (TeacherDao)new ProxyFactory(td).getProxyInstance();
		proxyDao.teach();
		
	

总结

其他代理方法

除了静态代理、动态代理、Cglib代理外,还有几种变体

防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。

缓存代理:当请求图片文件等资源时,先到缓存代理取,有则返回,没有则到公网或数据库里取,然后缓存(到时候写缓存的时候可以用)

远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用,远程代理通过网络和真正的远程对象沟通信息。(RPC?Feign?)

同步代理,主要使用在多线程中,完成多线程间同步工作。

行为型

模板方法

用我的话来说就是一些特定的模块,可能需要自定义一些算法,这些自定义的算法需要你根据你的使用场景进行自定义。而且这些实现模块的类,仅是一些小差别,如果差异太大,可能不适合这个模块方法。

AbstractClass 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现,其它的抽象方法

ConcreteClass 实现抽象方法,以完成算法中特点子类的步骤


public class PeanutSoyaMilk extends SoyaMilk

	@Override
	void addMetiral() 
		System.out.println("第二步: 加花生");
	




public class PureSoyaMilk extends SoyaMilk
	
	boolean addMeterial;
	
	public PureSoyaMilk(boolean addMeterail) 
		this.addMeterial = addMeterail;
	

	@Override
	void addMetiral() 
		System.out.println("第二步:加给");
	

	@Override
	boolean customerWant() 
		return addMeterial;
	
	
	



public class ReadBeanSoyaMilk extends SoyaMilk

	@Override
	void addMetiral() 
		System.out.println("第二步: 加红豆");
	



public abstract class SoyaMilk 

	final void make() 
		select();
		if(customerWant()) 
			addMetiral();
		
		soak();
		beat();
	
	
	void select() 
		System.out.println("第一步:选豆");
	
	
	abstract void addMetiral();
	
	void soak() 
		System.out.println("第三:浸泡");
	
	
	void beat() 
		System.out.println("第四:研磨");
	
	
	// 这玩意就叫作钩子,可以自定义,但对其他代码没有影响
	boolean customerWant() 
		return true;
	


public class TestSoyaMilk 

	
	@Test
	public void testSoyaMilk() 
		
		System.out.println("花生牛奶 ~~~ ");
		SoyaMilk peanut = new PeanutSoyaMilk();
		peanut.make();
		
		System.out.println("\\n\\n\\n 红豆牛奶 ~~~ ");
		SoyaMilk redBean= new ReadBeanSoyaMilk();
		redBean.make();

		System.out.println("\\n\\n\\n 纯大豆蛋白 ~~~ ");
		SoyaMilk pure = new PureSoyaMilk(false);
		pure.make();
		
		
	

总结

这是尚硅谷的总结

模板方法的注意事项和细节:

  1. 基本思想是:算法只存在于一个地方,也就是父类中,容易修改,需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  2. 实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用
  3. 即统一了算法,也提供了很大的灵活性,父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
  4. 该模式的不足之处,每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
  5. 一般模板方法都加上final关键字,防止子类重写模板方法
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系统的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理

命令模式


public interface Command 
	
	void execute();
	
	void undo();




public class TestCommand 
	
	@Test
	public void testCommand() 
		
		LightReceiver lightReceiver = new LightReceiver();
		
		LightOnCommand onCommand = new LightOnCommand(lightReceiver);
		LightOffCommand offCommand = new LightOffCommand(lightReceiver);
		
		// 新建一个遥控器
		RemoteController remoteController = new RemoteController();
		
		// offCommand 和 onCommand是 分别执行的操作,但我觉得后面用这个写一个平台的时候,可以用多种方式,而不是
		// 而不是这种true or false的形式
		remoteController.setCommand(0, onCommand, offCommand);
		
		remoteController.onButtonWasPushed(0);
		
		remoteController.undoButtonWasPushed();
		
		remoteController.offButtonWasPushed(0);
		
		remoteController.undoButtonWasPushed();
		
		
		// 这里的热销逻辑是,将每次使用的命令保存为全局变量,每次撤销直接使用全局变量即可 
		
	



public class RemoteController 

	
	Command[] offCommands;
	Command[] onCommands;
	
	// 这就是一个临时的引用 
	Command undoCommand;

	public RemoteController() 
		offCommands = new Command[5];
		onCommands = new Command[5];
		
		for (int i = 0; i < 5; i++) 
			offCommands[i] = new NoCommand();
			onCommands[i] = new NoCommand();
		
	
	
	public void setCommand(int no,Command onCommand, Command offCommand) 
		onCommands[no] = onCommand;
		offCommands[no] = offCommand;
	
	
	public void onButtonWasPushed(int no) 
		onCommands[no].execute();
		undoCommand = onCommand

以上是关于Java 23 种设计模式学习的主要内容,如果未能解决你的问题,请参考以下文章

Java 23 种设计模式学习

不得不会的23种Java设计模式——单例模式

精心整理的Java 23种设计模式知识要点

DAY1-23种设计模式学习开始

浅析Java中的23种设计模式

java23种设计模式之一: 策略模式