面向对象设计原则、Java设计模式
1.面向对象七大设计原则
1. 开闭原则:对扩展开放,对修改关闭
将可能发生变化的类设计为抽象类,后续变化时,新增类来实现新的业务
可复用性和可维护性强,降低维护代码带来的新风险。
2. 依赖倒置原则:程序要依赖于抽象接口,不依赖与具体实现。
便于理解,提高了代码可读性
3. 单一职责原则:一个类只做一件事,减少与类无关的功能
如ArrayLsit和LinkedList,不同实现,不同功能。
4. 里式替换原则:多态
也就是多态,父类出现的地方都可以用子类替换
参数为List,传入ArrayList,LinkedList,Vector都可以
里式替换需要将父类设计为抽象类或接口,子类实现父类中所有方法
5. 迪米特原则:减少对其他对象的了解
对其他对象要最少了解。
不和陌生人说话,方法中创建的对象属于陌生人
6. 接口隔离原则:功能隔离,不把很多功能加载一个接口上
使用多个专门的接口,不要将多余功能加在一个接口上
7. 合成复用原则:优先使用组合
优先使用组合/聚合(has-a),其次考虑继承(只有is-a关系才使用继承)
组合:员工类包含部门,部门包含员工集合
继承:电脑 与 笔记本电脑,平板电脑,台式电脑等属于继承关系
2.Java 设计模式
概述
设计模式就是在长期的开发中,为了解决一些重复出现的问题,经过长期的总结优化,最终确定的一套解决方案。
优点:
- 提高了代码的复用性、可维护性、可靠性、可读性、灵活性
- 提高了程序员的思维能力、编码能力,设计能力,能够设计出标准化的程序
设计模式根据工作类型可划分为:创建型模式、结构性模式、行为型模式
- 创建型模式
如何创建对象?将对象创建与使用分离- 单例 singleton:单例类只创建一个对象,所有线程共享
- 原型 prototype:每次新的请求都会对原型对象进行复制,得到新的对象
- 工厂方法 Factory Method:定义一个用于创建产品的接口,由子类决定生产什么产品
- 抽象工厂 AbstractFactory:提供一个创建产品族的接口,其每个 子类可以生产一系列相关的产品
- 建造者 Builder:将一个复杂对象分解成多个相对简单的部分,然
后根据不同需要分别创建它们,最后构建成该复杂对象
- 结构型模式
类与类如何组合成更大的结构- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客 户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口, 使得原本由于接口不兼容而不能一起工作的那些类能一起工作
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组 合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合 度
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子 系统更加容易被访问
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复 用
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象 和组合对象具有一致的访问性
- 行为型模式
类和类之间如何协作、分工- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定 义该算法的某些特定步骤
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下 一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数 据,而不暴露聚合对象的内部表示
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的 解释方法,即解释器
常用设计模式
1.单例模式
设计一个类时,让他只能创建一个对象(节省资源,保证数据一致性)
单例模式需要实现的功能
- 单例类只能有一个对象
- 单例对象由单例类内部自己创建
- 单例类对外提供一个访问对象的方法
单例实现:
- 构造方法私有化,在其他类中不能使用new创建对象
- 向外界提供一个静态的方法,用来获取单例对象
1.1饿汉式单例
单例类一加载,就创建了对象,在调用getInstance
方法之前就存在对象
public class HungrySingleton {
private static HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return singleton;
}
}
1.2懒汉式单例
懒汉式单例:类加载时,并不会去创建对象,只有第一次访问时,才去创建,存在线程安全问题
解决懒汉式单例线程安全:
- 加锁:效率低;只有第一次是创建对象,其他都获取对象
- volatile + 双重检索 :提高安全性、可靠性;提升了效率
一般的加锁方式—效率低下
public class LazySingleton {
private static LazySingleton singleton = null;
private LazySingleton() {}
//方法加锁
//效率低:创建对象只是第一次,后面都是获取对象。每次获取对象只能有一个线程获取,
public static synchronized LazySingleton getInstance(){
if(singleton == null){
singleton = new LazySingleton();
}
return singleton;
}
//代码块加锁 : 效率也低
public static LazySingleton getInstance() {
synchronized (LazySingleton.class) {
if (singleton == null) {
singleton = new LazySingleton();
}
}
return singleton;
}
}
双重检索 + volatile — 效率提升
volatile
使用volatile修饰单例对象,主要是为了实现有序性
因为创建对象,给引用赋值的过程,可能被拆分为3个指令
①new Singleton():在内存中申请空间。(这条不会被重排)
②调用构造器初始化对象
③将对象地址赋给引用变量(有可能被重排到第二步执行,造成引用变量执行的是一个半成品对象)
双重检索
- 多个线程第一次进到getInstance方法时,判断sington为null,其中一个线程获得锁,进入代码块中去创建对象,之后释放锁,其他进入第一次检索的线程也会去获取锁,拿到锁之后,要潘判断singleton是否为空,不为空说明已经有线程创建了对象,直接返回就行
- 之后的线程访问getInstance方法时,第一层检索不为空直接返回单例对象
总结:
- 第一层检索是检索单例对象是否被创建
- 第二层检索是多线程第一次进入getInstance()时,判断是否有其他线程已经创建过对象了
package com.example.java_hign.framework.singleton;
public class LazySingleton {
//volatile 为了保证有序性
private static volatile LazySingleton singleton = null;
private LazySingleton() {}
//双重检索 + volatile
public LazySingleton getInstance(){
//多个线程第一次进入该方法,singleton 为 null
if(singleton == null){
//还没有初始化singleton时,只允许一个线程来创建
synchronized (LazySingleton.class){
//其他线程进来,如果singleton已经被初始化,就直接返回
if(singleton == null){
singleton = new LazySingleton();
}
}
}
//后面线程获取对象时,直接返回创建好的singleton
return singleton;
}
}
2.工厂模式
工厂:批量创建对象 (属于设计模式中的创建型模式)
定义一个创建产品的工厂接口,具体的产品创建由接口的实现类来完成
并不对外显示创建对象的逻辑,只提供一个接口指向新创建的对象
思想:创建对象和使用对象相分离
什么时候使用工厂模式?
想要在不同的条件下创建不同的对象时
1.简单工厂模式
简单工厂模式中创建实例的方法是static修饰,简单工厂模式也叫静态工厂模式
何时使用简单工程模式?
- 需要创建的对象少,只需要一个工厂类就可以完成
- 客户端不需要关注对象的创建过程
优点
调用者想创建一个对象,只需要知道其名称即可
使用简单工厂
测试
public class Test {
public static void main(String[] args) {
SimpleFactory productFactory = new SimpleFactory();
Product phone = productFactory.getProduct("com.example.java_hign.factory.Phone");
Product pad = productFactory.getProduct("com.example.java_hign.factory.Pad");
Product computer = productFactory.getProduct("com.example.java_hign.factory.Computer");
phone.displayInfo();
pad.displayInfo();
computer.displayInfo();
}
}
2抽象工厂模式
抽象工厂模式是用来创建工厂的(像超级管理员一样,可以创建不同的管理员,再由管理员去做其他事)
抽象工厂中
- 接口用来创建一组相关对象的工厂
- 每个生成的工厂可以按照简单工厂模式来生成对象
使用抽象工厂
1.创建多个产品接口及实现
阅读产品
电子产品
2. 创建抽象工厂类
3. 创建产品对应的简单工厂
阅读产品的工厂
电子产品的工厂
4. 创建工厂生成类
传入工厂的全类名,即可创建指定工厂
5.测试
5.1 使用抽象工厂生成电子产品工厂
使用电子产品工厂生成所属产品对象
public class Test {
public static void main(String[] args) {
//获取电子产品的简单工厂
AbstractFactory factory = FactoryProducer.getFactory(
"com.example.java_hign.factory.abstractfactory.ElectronicSimpleFactory");
//获取电子产品工厂中的产品
ElectronicProduct computer = factory.getElectronicProduct(
"com.example.java_hign.factory.abstractfactory.Computer");
ElectronicProduct pad = factory.getElectronicProduct(
"com.example.java_hign.factory.abstractfactory.Pad");
ElectronicProduct phone = factory.getElectronicProduct(
"com.example.java_hign.factory.abstractfactory.Phone");
//测试产品
computer.displayInfo();
pad.displayInfo();
phone.displayInfo();
}
}
5.2 使用抽象工厂生成阅读产品工厂
使用阅读产品工厂生成所属产品对象
public class Test {
public static void main(String[] args) {
//获取阅读产品的简单工厂
AbstractFactory factory = FactoryProducer.getFactory(
"com.example.java_hign.factory.abstractfactory.ReadSimpleFactory");
//获取阅读产品工厂中的产品
ReadProduct book = factory.getReadProduct(
"com.example.java_hign.factory.abstractfactory.Book");
ReadProduct ebook = factory.getReadProduct(
"com.example.java_hign.factory.abstractfactory.EBook");
book.display();
ebook.display();
}
}
3.代理模式
当我们想完成某件事,但有不想直接访问目标时,可以通过代理来帮忙完成
(租房子,自己一个一个找房东(目标)效率太低,通过中间商来帮忙完成)
代理的优点:
- 保护目标对象,不直接访问目标对象
- 对访问者提供额外操作(代理可扩展目标对象的功能)
- 降低了访问对象和目标对象的耦合度
3.1静态代理
实际中用的并不多
静态代理类必须实现和目标类一样的接口
扩展性很差
3.2动态代理
代理类不需要实现与目标类相同的接口,可以代理任意目标类
但要求目标类都实现接口
动态代理可以对目标类统一进行处理(添加日志)
3.2.1jdk代理
纯反射机制实现,动态获取目标类的方法
动态生成代理对象,目标类必须实现接口
步骤:
- 编写目标接口(UserDao)
- 编写目标类(UserDaoImpl)
- 编写动态代理类,实现InvocationHandler接口,重写invoke方法
- 测试类中生成代理类的对象
测试类
public class Test {
public static void main(String[] args) {
UserDao vipUser = new VipUserDaoImpl();
InvocationHandler proxy = new DynamicProxy(vipUser);
//真正的创建动态代理对象
UserDao userDao = (UserDao) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(),VipUserDaoImpl.class.getInterfaces() , proxy);
//调用的是invoke方法
userDao.save(new User(20,"张飞"));
}
}
3.2.2Cglib代理
目标类不需要实现接口,采用底层的动态字节码技术,为目标类动态生成子类,在子类中拦截父类方法的调用(采用方法拦截技术实现)。
使用了继承,不能代理final修饰的类
Cglib代理实现:
- 引入 cglib 的 jar 文件,spring核心包中集成了Cglib,引入
spring-core-xxx.jar
就可以 - 在内存中动态创建子类
- 不能代理final类,无法创建子类
- 无法拦截目标对象中的final 和 static 修饰的方法
总结:
静态代理是在代码中实现,代理类必须实现和目标类相同的接口,目标类接口修改或增加,代理类也要跟着改。
动态代理在运行时生成,代理类实现InvocationHandler接口,可以代理任意的目标类,要求目标类必须实现接口。
动态代理较于静态代理 : 减少了对接口的依赖,降低了耦合度。