原则
核心:尽量面向接口 -> 最终目标:松耦合。
一,单一职责原则
每个(类/方法)只负责属于自己的所有任务,增加代码可靠性,防止应修改代码导致其他类错误。在可行的条件下应尽量遵守。
二,接口隔离原则
1,客户端不应该依赖不需要的类。
2,类对类的依赖,应该建立在最小的接口,即剔除较高层接口中不需要的接口。故接口设计时,应有分解的意识。
三,依赖倒置原则
核心:面向接口编程
1,高层模块不依赖低层模块,而应依赖抽象。
2,抽象不依赖细节,细节应依赖抽象。
原因:抽象即设计,其稳定性高,细节由实现类解决即可。
例:person接受一个sender抽象类,phone类实现sender类,传入参数时传入phone类,即可实现一个细节模块依赖抽象接口的实例。
四,里氏替换原则
子类尽量不重写父类方法。
任何使用基类的地方,都可以用子类直接替换,那么满足里氏替换。
要注意,继承是一个增耦的过程。
五,开闭原则
对拓展开放,对修改关闭。
使用方不应该修改,应该拓展传入方。
六,迪米特法则
一个类对依赖的类知道的越少越好,调用者不需要知道太多所依赖类的操作。
七,合成复用原则
尽量多用聚合,组合替代继承。
当你有一个类A需要使用B的方法,相比起继承B,更好的解决办法是,我们将B作为A的成员变量进行依赖注入。或者,将B作为A一个方法的传入值。
设计模式
1,单例设计模式
保证在软件系统中,只存在一个对象实例。
饿汉式
1,私有化构造方法防止外部new,创建私有静态单例,只能通过getInstance访问。
优:避免线程同步问题。
缺:类装载时就完成实例化,没有达到懒加载,(由于我们无法完全确定类加载的时机)可能造成内存浪费。
private Singleton() {}
private final static Singleton instance = new Singleton();
2,私有化构造方法,在静态代码块中创建静态单例对象。(与第一种相似)
private Singleton(){}
private static Singleton instance;
static{
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
懒汉式
1,私有构造方法,getInstance时判断是否有实例
优:懒加载。
缺:线程不安全,可能破坏单例模式。
private Singleton();
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
2,在上一种的getInstance加同步锁
优:解决线程问题。
缺:效率很低。
3,双重检查
优:保证线程安全,保证无内存浪费,推荐使用。
这里的实例需要volatile,保证工作内存与主内存区的数据一致性,保证线程安全。
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
4,静态内部类
private Singleton(){}
//静态类只加载一次
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
优点:外部类装载不会导致内部类装载。利用类装载机制保证线程安全。保证懒加载。
枚举(推荐)
enum Singleton{
INSTANCE;
public void method(){
//TODO
}
}
简单,线程安全,防止反射破坏单例。
2,简单工厂模式
根据开闭原则,若我们需要用到大量的创建某类对象时,我们会创建一个工厂对象统一管理创建。那么就会使使用方保持代码不变,对使用方是透明的。
3,工厂方法模式
由多个工厂类实现工厂接口,相当于替代了if…else…,转而利用多态实现类别控制。
4,抽象工厂模式
把工厂要生产的产品子类分组,属于一个组的不同产品由一个工厂的不同方法实现,避免了工厂子类过多的情况。
工厂模式:一般都是传入不同参数,根据参数返回不同对象的情况。不依赖具体类,比如直接new类。
5,原型模式
用原型实例指定创建对象的种类,并通过拷贝创建对象。
1,原型类,声明一个克隆自己的接口。
2,具体原型类,实现上述接口。
3,使用者,调用克隆接口。
例: bean的多例prototype创建。
深拷贝的实现。
1,clone时同时clone依赖类。
public class DeepProtoType implements Serializable,Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
//深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
DeepProtoType deep = (DeepProtoType) super.clone();
deep.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deep;
}
}
2,序列化。
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
//序列化深拷贝
public Object deepClone() {
try (ByteArrayOutputStream bos =
new ByteArrayOutputStream();
ObjectOutputStream oos =
new ObjectOutputStream(bos);
) {
//对象以流的形式输出
oos.writeObject(this);
//对象以流的形式输入,反序列化
try (ByteArrayInputStream bis =
new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois =
new ObjectInputStream(bis)) {
DeepProtoType deepProtoType = (DeepProtoType) ois.readObject();
return deepProtoType;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
6,建造者模式
也叫生成器模式。可以将复杂对象的建造过程抽象出来,使得这个抽象过程的不同实现可以构造出不同的对象。
1,产品角色:最后的产品对象。
2,抽象建造者:一个产品的各个部分的抽象接口(一套建造流程)。
3,具体建造者:实现产品各部分的具体构建或装配方式(建造的具体方案)。
4,指挥者:构建一个使用抽象建造者的对象。其职能为控制生产过程,隔离用户与生产过程,使生产过程对用户透明。
//抽象建造类
abstract class Builder{
//内部组合一个产品
Product product;
//抽象建造方法
partA();
partB();
.....
partN();
getResult();
}
//客户端只需要调用Director即可获取最后的产品。
class Director{
//组合一个buider实现类
Builder builderImpl;
}
实例:StringBuilder
优点:由于定义好了建造方法模板,所以对于不同的产品,只需要增加子类实现即可,满足开闭原则。同时与客户端解耦,不需要更改客户端代码。
抽象工厂模式-建造者模式:
抽象工厂模式实现对一个产品家族的实现,不关心建造过程。
建造者模式关心建造的过程,需要知道具体构造的方法。
7,适配器模式
可类比充电器转接头。
适配器模式旨在将一个类的接口转化为客户端期望的另一种接口形式。主要是为了兼容性。其别名为包装器(wrapper)
主要有三类:
类适配器模式,对象适配器模式,接口适配器模式
原理: 被适配者对用户透明,解耦。用户调用适配器接口,适配器再调用被适配器接口。
类适配器
使用适配器继承被适配器
对象适配器
不再继承,而是选择聚合被适配器,再实现适配器接口。符合合成复用原则。
接口适配器模式
或称缺省适配器模式。
如果我们不需要实现所有的抽象方法,那么对于不需要的方法我们可以先实现空方法,然后子类有选择地重写方法。
实例:SpringMVC中的Handler。(下图是简化之后的doDispatchServlet实现过程)
首先是getHandler(),方法内部进行Adapter子类列表的遍历,每次调用Adapter子类的support()方法判断是否是配此controller,若匹配则传入这个子类的handle(),进行下一步操作。
这种模式下,我们想要多支持一种controller,只需要多写一种Adapter子类,不需要修改任何代码,完成了对controller的适配。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYXR5MqR-1639906270615)(C:\Users\SANZONG\AppData\Roaming\Typora\typora-user-images\image-20211213195932331.png)]
8,桥接模式
将抽象和实现放在不同层次,使得他们能够各自独立变化。
可以解决多重继承,把一层继承转化为接口实现类,避免类爆炸。
桥接模式的缺点
(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
//品牌的抽象接口
public interface Brand {
void open();
}
//品牌子类
public class Xiaomi implements Brand{
@override
public void open(){
print("Xiaomi open");
}
}
public class Huawei implements Brand{
@override
public void open(){
print("Huawei open");
}
}
//手机抽象父类
public abstract class Phone {
private Brand brand;
public Phone (Brand brand){
this.brand = brand;
}
public void open(){
//调用传入的brand,桥接起品牌的接口与手机子类的实现。
brand.open();
}
}
//手机子类
public class SmartPhone extends Phone {
SmartPhone(Brand brand) {
super(brand);
}
public void open() {
//调用父类接受的brand的open().
//Phone桥接了SmartPhone的方法与Phone接受的brand接口实例。
super.open();
}
}
每次增加手机类型或品牌只需要新建一个类。
实例:JDBC中使用。
9,装饰者模式
可以类比成一种饮品 不加或加很多种调料 的任意组合。
使用装饰者模式,最后装饰完成的结构类似于:
调料一:
调料一:
调料二:
饮品一
//首先我们完成饮料主体部分
public abstract class Drink {
public String des;
private float price;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public void setPrice(float price) {
this.price = price;
}
public float getPrice(){
return price;
}
public abstract float cost();
}
//饮料子类咖啡
public class Coffee extends Drink{
@Override
public float cost() {
//返回我这种咖啡的价格,适配所有咖啡子类
return getPrice();
}
}
//咖啡的实现子类F咖啡
public class FirstCoffee extends Coffee{
public FirstCoffee() {
setDes("F coffee");
setPrice(1.0F);
}
}
//装饰类内聚合一个饮料主体,这个饮料可以是加过调料之后的
public class Decorator extends Drink{
//这是被装饰的主体
private Drink obj;
public Decorator(Drink obj) {
this.obj = obj;
}
@Override
public float cost() {
//把这一层加的调料的价格和(已经加过调料的)饮料价格相加,递归。
return getPrice() + obj.getPrice();
}
public String getDes(){
return des + " " + getPrice() + " " + obj.getDes();
}
}
//调料类继承装饰类
public class Chocolate extends Decorator{
public Chocolate(Drink obj) {
setDes("chocolate");
setPrice(2.0F);
}
}
//调用
public static void main(String[] args) {
//构造饮料主体
Coffee coffee = new FirstCoffee();
//加调料
Drink order = new Chocolate(new Chocolate(coffee));
//Cho:
// Cho:
// FirstCoffee
}
我们发现在装饰类中既发生了组合又发生了继承。
继承的是装饰。
组合的饮料的半成品。
实例:JAVA io,FilterInputStream。
10,组合模式
又称部分-整体模式,用树形结构表示部分与整体层次。
实现一个如下的树形的架构的模式:
xxxx大学
------xxx学院
-----------------xxx系
------xxxxx学院
//我们使用一个组合类描述所有类的基本操作,抽象类or接口都可以
public abstract class Component {
private String name;
private String des;
public Component(String name, String des) {
this.name = name;
this.des = des;
}
public String getName() {
return name;
}
protected void add(Component component){
//TODO
throw new UnsupportedOperationException();
}
protected abstract void print();
}
//大学类作为最顶级节点,继承Component,重写或添加属于大学类自己的方法,并实现Component的公共抽象方法
public class University extends Component{
//重点在于组合一层不指定实际类型的Component集合,形成树形结构。
//实际只允许添加College类
List<Component> components = new ArrayList<Component>();
public University(String name, String des) {
super(name, des);
}
//重写
protected void add(Component component){
components.add(component);
}
//实现
@Override
protected void print() {
System.out.println(getName());
components.forEach(Component::print);
}
}
//第二层的类,作为上一层的集合中的成员,并重写或实现自己的方法。
public class College extends Component{
List<Component> components = new ArrayList<Component>();
public College(String name, String des) {
super(name, des);
}
//重写
protected void add(Component component){
components.add(component);
}
//实现
@Override
protected void print() {
System.out.println(getName());
components.forEach(Component::print);
}
}
//叶子,不需要再聚合,实现自己的方法。
public class Department extends Component{
public Department(String name, String des) {
super(name, des);
}
@Override
protected void print() {
System.out.println(getName());
}
}
//实例
public static void main(String[] args) {
Component uni = new University("xx大学", "大学des");
Component college1 = new College("xx学院1", "学院1des");
Component college2 = new College("xx学院2", "学院2des");
college1.add(new Department("xxx系","xx"));
uni.add(college1);
uni.add(college2);
uni.print();
}
目的是实现树形结构,实现层与层之间的独立的管理方法,也可以同时有一套通用的管理方法。方便添加新的类。但是违背了依赖倒置原则。
实例:HashMap
11,外观模式(facade,过程模式)
场景:同意管理许多子系统的情况,子系统可能出错,可能被移除或添加。如家庭影院管理。
解决:定义一个一致性接口,屏蔽子系统的细节。
应用:mybatis
12,享元模式(蝇量模式)
场景:将一个网站根据不同要求进行多种不同要求的定制。(最终结构的相似度高)
享元模式的思想是共享来解决重复对象浪费,比如数据库连接池,常量池等池技术都是享元模式。
常用于系统底层开发。
内部对象:共享信息,再享元对象内部不会改变。
外部对象:不可共享的状态。
实现:首先需要准备好具体实例Obj1,Obj2继承抽象类Obj完成内部状态,然后对Obj1,Obj2实现各自的方法完成外部状态,将实例放入Factory的池中即可实现一个享元对象factory。
实例:Integer,String等的常量池。
13,代理模式(proxy)
为一个对象提供一个代理,以控制这个对象。
好处:可以扩展这个对象的功能。
1,静态代理
代理与被代理对象需要实现或继承相同接口或父类。
利用聚合实现代理。
优点:可以扩展功能。
缺点:1,由于需要有公共父类,或出现很多代理类。2,增加接口,都需要实现,比较麻烦。
//公共接口
public interface ITeacher {
void teach();
}
//被代理者
public class Teacher implements ITeacher{
@Override
public void teach() {
System.out.println("teach");
}
}
//代理者
public class TeacherProxy implements ITeacher{
private ITeacher teacher;
public TeacherProxy(ITeacher teacher) {
this.teacher = teacher;
}
@Override
public void teach() {
//实现代理扩展功能,用同样的方法做更多的事。
System.out.println("start");
teacher.teach();
System.out.println("end");
}
}
//调用者
public static void main(String[] args) {
ITeacher proxy = new TeacherProxy(new Teacher());
proxy.teach();
}
2,动态代理(JDK代理,接口代理)
要求:目标对象需要实现接口。
//公共接口
public interface ITeacher {
void teach();
}
//被代理者
public class Teacher implements ITeacher{
@Override
public void teach() {
System.out.println("teach");
}
}
//代理器
public class ProxyFactory {
private Object teacher;
public ProxyFactory(Object teacher) {
this.teacher = teacher;
}
public Object getProxyInstance() {
//ClassLoader loader: 指定当前目标对象使用的类加载器。
//Class<?>[] interfaces:目标对象实现的接口类。
//InvocationHandler h: 事件处理,执行目标对象方法时触发,会把当前执行的目标对象方法作为参数传入
return Proxy.newProxyInstance(teacher.getClass().getClassLoader(),
teacher.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//扩展功能
System.out.println("start");
//反射调用目标对象的方法,实现原对象的正在调用的方法
return method.invoke(teacher, args);
}
});
}
}
//调用对象
public static void main(String[] args) {
ITeacher proxyInstance = (ITeacher) new ProxyFactory(new Teacher()).getProxyInstance();
proxyInstance.teach();
}
Cglib代理
也称子类代理,当需要代理的对象没有实现接口时可以用目标子类对象实现代理。
注意:cglib无法代理final类
实现方法:通过字节码处理框架asm转换字节码形成行类。
//普通类
public class Teacher {
public void teach(){
System.out.println("teach");
}
}
//代理工厂,利用拦截+反射实现代理对象方法,利用Enhancer类创建代理子类。
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//target的代理
public Object getProxyInstance(){
Enhancer enhancer = new Enhaner();
//设置父类
enhancer.setSuperclass(target.getClass());
//设置回调
enhancer.setCallback(this);
//返回子类代理对象。
return enhancer.create();
}
//调用目标对象方法
public Object intercept(Object arg0,Method method,Object[] arg2,MethodProxy arg3) throws Throwable{
System.out.println("start");
Object ret = method.invoke(target, arg2);
return ret;
}
}
实例:双重缓存,远程调用rpc。