java设计模式
作者:互联网
七大原则
-
开闭原则:是指一个软件实体如类、模块和函数应该对扩展开放, 对修改关闭
-
依赖倒置原则:是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象而不依赖于具体。
-
单一职责原则:是指一 个 Class/Interface/Method 只负责一项职责。
-
接口隔离原则:是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
-
迪米特法原则(最少知道原则):是指一个对象应该对其他对象保持最少的了解。
-
里氏替换原则:是指一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变
-
合成复用原则:是指尽量使用对象组合(has-a)或聚合(contanis-a),而不是继承关系达到软件复用的目的
单一职责原则
含义:
单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。换一种说法,一个类只负责一项职责,应该仅有一个引起它变化的原因。总体来说就是一个Class/Interface/Method 只负责一项职责。
核心思想:
Class/Interface/Method 只负责一项职责
作用(优点):
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
提高类的可读性,提高系统的可维护性;
变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
实现方式:
单一职责原则是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
开闭原则
含义:
开闭原则(Open-Closed Principle, OCP)是指一个软件实体 如类、模块和函数应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统,例如:我们版本更新,我尽可能不修改源代码,但是可以增加新功能。
核心思想:
面向抽象编程。
作用(优点):
可以提高代码的可复用性:我们可以在软件完成以后,仍然可以对软件进行扩展,加入新的功能,非常灵活。因此,这个软件系统就可以通过不断地增加新的组件,来满足不断变化的需求。
可以提高软件的可维护性:由于对于已有的软件系统的组件,特别是它的抽象底层不去修改,因此,我们不用担心软件系统中原有组件的稳定性,这就使变化中的软件系统有一定的稳定性和延续性。如:一人模块变化,会对其它的模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化。
实现方式:
实现开闭原则的关键就在于“抽象”。把系统/软件的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
通过接口或抽象类约束扩散,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。
参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏替换原则的一个要求。
抽象层尽量保持稳定,一旦确定就不要修改。
依赖倒置原则
含义:
依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层(调用层)模块不应该依赖底层(被调用层)模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。即面向接口编程,不要面向实现编程。依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构要比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。
核心思想:
面向接口编程,不要面向实现编程。
作用(优点):
减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。
实现方式:
传递依赖关系有三种方式:接口传递(方法注入)、构造方法传递和setter方法传递
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
低层模板尽量都要有抽象类或接口,或者两个都有,程序稳定性更好。
变量的声明类型尽量都是抽象类或接口,这样我们的变量变量引用和实际对象间就存在一个缓冲层,有利于程序的扩展和优化。
继承时遵循里氏替换原则。
接口隔离原则
含义:
接口隔离原则(Interface Segregation Principle, ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性
和可维护性
核心思想
高内聚低耦合
作用(优点):
接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性、可扩展性和可维护性。
接口隔离原则跟单一职责原则区别:
单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。
单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
实现方式:
一个类对一类的依赖应该建立在最小的接口之上。
建立单一接口,不要建立庞大臃肿的接口。
提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度,接口过小则会造成接口数量过多,使设计复杂化)。
多花时间去思考、要考虑业务模型,包括以后有可能发生变更的地方还要做一些预判。所以对于抽象、对业务模型的理解是非常重要的
迪米特原则
含义:
迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP)尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话(出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。)
核心思想
降低类之间的耦合度
作用(优点):
降低了类之间的耦合度,提高了模块的相对独立性。由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
里氏替换原则
含义:
里氏替换原则(Liskov Substitution Principle,LSP)是指如果对每一个类型为 T1 的对象 o1,都有 类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没 有发生变化,那么类型 T2 是类型 T1 的子类型。换一种理解方式,可以理解为一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。根据这个理解,我们总结一下:(实现方式:)
引申含义:子类可以扩展父类的功能,但不能改变父类原有的功能
子类可以实现父类的抽象方法,
不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入 参数更宽松。
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输 出/返回值)要比父类更严格或相等。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
核心思想:
子类中不应该重写父类的方法
作用(优点):
约束继承泛滥,里氏替换原则是实现开闭原则的重要方式之一,是开闭原则的一种体现。克服了继承中重写父类造成的可复用性变差的缺点。它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。加强程序的健壮性,同时变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险
合成复用原则
含义:
合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)、聚合(contanis-a),而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。继承我们叫做白箱复用,相当于把所有的实现细节暴露给子类。组合/聚合也称之为黑箱复用,对类以外的对象是无法获取到实现细节的。要根据具体的业务场景来做代码设计,其实也都需要遵循 OOP
核心思想:
尽量使用聚合、组合的方式,而不是使用继承。
实现方式:
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
聚合与组合的区别:
聚合是用来表示“拥有”关系或者整体与部分的关系。
组合则是表示一种强得多的“拥有”关系,在组合里,部分与整体的生命周期是一样的。一个组合的新的对象完全拥有对其组成部分的支配权,包括它们的创建和湮灭等。一个组合关系中的成分对象是不能与另一个组合关系共享的。一个组成部分在同一个时间内只能属于一个组合关系。
复用的基本方式有两种:
组合/聚合
继承
复用两种方式的区别:
组合/聚合是将已有的对象纳入到新对象中,使之成为新对象的一部分,优点:新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。每个新的类可以将焦点集中在一个任务上。复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。缺点:通过使用这种方式复用建造的系统会有较多的对象需要管理。继承是面向对象特有的复用工具,而且也最容易被滥用。继承复用通过扩展一个已有对象的实现来得到新的功能。优点:较为容易,因为父类的大部分功能都可以通过继承关系自动进入子类。修改或扩展继承而来的实现较为容易。缺点:破坏了类的封装性,因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,修改父类就会形成链锁反映,这不利于类的扩展与维护。限制了复用的灵活性,从父类继承而来的实现是静态的,在编译时已经定义,所以不可能在运行时间内发生改变,因此也没有足够的灵活性。
23种设计模式
单例模式
饿汉式
由于对象在类里面才能被创建,而且是静态的,所以,被创建的对象始终都是一个,由于在类装载时就被完成了实例化,所以没有线程安全问题,如果没有使用这个实例,会造成内存浪费
//饿汉式 class Singleton1{ public String name; private static final Singleton1 instance = new Singleton1(); private Singleton1(){ System.out.println("Singleton1,被构建对象"); } public static Singleton1 getInstance(){ return instance; } }
懒汉式
线程不安全
起到了懒加载效果,但是只能在单线程使用,多线程会不安全,因为当多个线程并发同时判断instance为空时,就会相应的实例化多个对象。
class Singleton { //线程不安全 private static Singleton instance; private Singleton() {} public static Singleton getInstance() { //调用时才实例化对象,懒汉式 if(instance == null) { instance = new Singleton(); } return instance; } }
线程安全
上面线程不安全,那上锁不就好了,使用synchronized关键字。 这样虽然解决了线程安全,但其实实例化操作只做一次,而获取实例(即getInstance)的操作是很多次的,把调用的方法加上同步,会大大降低效率。
class Singleton { //线程安全 private static Singleton instance; private Singleton() {} //synchronized同步处理 public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
class Singleton { //双重检查 private static volatile Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if(instance == null) { //判断是否实例化 synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; //否则直接return } }
class Singleton { private static volatile Singleton instance; private Singleton() {} //静态内部类,包含一个静态属性:Singleton private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } //对外公有的静态方法,直接返回SingletonInstance.INSTANCE public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }
使用枚举也可以完成单例模式
enum Singleton { INSTANCE; //属性 public void say() { System.out.println("记得三连~"); } }
public static void main(String[] args) { Singleton instance1 = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance1 == instance2); System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); instance1.say(); }
测试结果
- true
- 460141958
- 460141958
- happy!
代理模式
静态代理
- 优点
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。 - 缺点
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
接口
public interface Rent { void rent(); }
目标类
public class Host implements Rent{ @Override public void rent() { System.out.println("房屋出租"); } }
代理类
public class HostProxy implements Rent{ Host host; public HostProxy(Host host1){ this.host=host1; } @Override public void rent() { System.out.println("事务开始"); host.rent(); System.out.println("提交"); } }
客户
public class Client { public static void main(String[] args) { //静态代理 Host host = new Host(); HostProxy hostProxy = new HostProxy(host); hostProxy.rent(); } }
动态代理jd我们可以通过ProxyHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等当前非常流行的面向切面的编程(Aspect Oriented Programming, AOP),其核心就是动态代理机制。
沿用静态代理的接口已经目标类
动态的代理类(内部类方式)
public class HostProxyRun { private final Object target; public HostProxyRun(Object target){ this.target=target; } /** * 需要的参数分别为 * ClassLoader loader 代理类的类加载器 * Class<?>[] interfaces 代理类的需要实现的接口列表 * InvocationHandler h 实现了InvocationHandler的类的实例 * @return */ public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); Object invoke = method.invoke(target, args); System.out.println("代理提交"); return invoke; } }); } }动态的代理类(实现InvocationHandle接口的方式)
public class ProxyInvocationHandle implements InvocationHandler { private final Object target; public ProxyInvocationHandle(Object target) { this.target = target; } /** * 需要的参数分别为 * ClassLoader loader 代理类的类加载器 * Class<?>[] interfaces 代理类的需要实现的接口列表 * InvocationHandler h 实现了InvocationHandler的类的实例 * @return */ public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理开始"); Object invoke = method.invoke(target, args); System.out.println("代理提交"); return invoke; } }
客户端
public class Client { public static void main(String[] args) { //静态代理 Host host = new Host(); HostProxy hostProxy = new HostProxy(host); hostProxy.rent(); System.out.println("=================="); //动态代理的jdk实现方式1(内部类的方式) HostProxyRun hostProxyRun = new HostProxyRun(new Host()); Rent proxy = (Rent) hostProxyRun.getProxy(); proxy.rent(); System.out.println("==================="); //动态代理的jdk实现方式2(通过实现InvocationHandle接口的方式) ProxyInvocationHandle pih = new ProxyInvocationHandle(new Host()); Rent proxy2 = (Rent) pih.getProxy(); proxy2.rent(); } }
运行结果
- 事务开始
- 房屋出租
- 提交
- ==================
- 开始代理
- 房屋出租
- 代理提交
- ===================
- 代理开始
- 房屋出租
- 代理提交
动态代理(cglib实现)
- 此方式不要求被代理类为接口的实现类,cglib基于继承被代理类实现的
需要先引入相关依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>
被代理的类
public class HelloCglib { public void sayHello() { System.out.println("CGLIB动态代理模式!"); } }
cglib代理类
public class CglibProxy implements MethodInterceptor { /** * 指定cglib代理模式的代理类 */ private final Object target; public CglibProxy(Object target) { this.target = target; } public Object getProxy() { Enhancer enhancer = new Enhancer(); //设置超类方法 enhancer.setSuperclass(this.target.getClass()); //设置一个回调方法,用来设置哪个类为代理类,this表示当前类为代理类 enhancer.setCallback(this); //创建代理对象 return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB代理前"); Object object = proxy.invokeSuper(obj, args); System.out.println("CGLIB代理后"); return object; } }
客户端演示
public class HelloClient { public static void main(String[] args) { CglibProxy cglibProxy = new CglibProxy(new HelloCglib()); HelloCglib proxy = (HelloCglib) cglibProxy.getProxy(); proxy.sayHello(); } }
工厂模式
标签:Singleton,java,对象,代理,接口,复用,设计模式,public 来源: https://www.cnblogs.com/happy12123/p/16370639.html