一、六大设计原则
- 1.开闭原则
对扩展开发,对修改关闭。 - 2.里氏替换原则
子类可以扩展父类的功能,但不能修改父类的功能。 - 3.依赖倒置原则
高层模块不应该依赖底层模块,两者都应该依赖抽象模块,抽象不应该依赖细节。 - 4.单一职责原则
规定一个类应该有且只有一个原因使它发生改变,否则该类应该被拆分。 - 5.接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。 - 6.迪米特法则
也叫作最少知识原则
只跟你的朋友交谈,不跟陌生人说话。也就是当两个程序无须直接通行,就不应该发生相互调用,可通过第三方调用。
二、单例模式
定义:确保某一个类只有一个实例,而且自行实例化冰箱整个系统提供这个实例。
使用场景:如果某个类的创建需要消耗系统过多的资源,或者整个系统就只需要某个类的一个对象。这样就可以使用单例模式。
单例模式的几个关键点:
- 构造函数不对外开放
- 通过一个静态方法或者枚举返回单例对象
- 确保单例对象有且只有一个,尤其是多线程环境下
- 确保单例对象对象在反序列化时不会重新构建新的对象
单例中比较简单的写法有懒汉式单例、饿汉式单例,不过这两个已经不推荐使用,这里不再赘述了。这里说说用的比较多的几种单例写法。
1.Double Check Look(DCL)实现单例
/**
* double check lock实现单例
*/
public class Singleton {
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;
}
}
由于多线程安全问题,这种方式进行了两次判空,还增加了volatile 关键字,保证instance == null的操作都从主内存中读取。这种方式在第一次获取单例对象时耗时一点,之后的获取,效率比较高。但在单例对象唯一性方面,在某些情况还是会失效。
2.静态内部类实现单例
/**
* 静态内部类实现单例
*/
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
}
这种方式当第一次调用Singleton 时不会初始化单例对象,只有当第一次调用getInstance时才会初始化对象,这样既保证了对象的延迟加载 ,也保证了对象的唯一性 。所以这种方式是推荐的单例写法。
以上两个两种方式都存在一个相同的问题,就是反序列化的时候,都有可能会生成新的对象,如果要解决这个问题,可以在类中加入以下代码
private Object readResolve() throws ObjectStreamException{
return instance;
}
就是在readResolve方法中将对象单例对象返回回去,避免默认创建新的对象。
除了以上两种方式,还有枚举实现单例方式,和容器实现单例方式。
/**
* 枚举实现单例
*/
public enum SingletonEnum{
INSTANCE;
public void doSomething(){
// TODO: 2020/10/11
}
}
容器实现单例就是就利用容器将单例对象存放起来,用时直接从容器里面取,这种方式可以管理多个类型的单例对象,在使用时用统一的接口,可以降低用户的使用成本降低了代码的耦合度。
三、Builder模式
定义:将一个复杂的对象与它的表现分离,使得同样的构建过程可以创建不同的表现。
使用场景:
-
相同的方法,不同执行顺序,产生不同的事件结果时。
-
多个部件,都可以装配到一个对象中,但产生的运行结果又不相同时。
-
对象类非常复杂,构建顺序不同会产生不同的作用。
Builder模式有三个主要角色
- 产品对象
- Builder类,拥有产品对象的实例,负责将从Director对象收集的产品信息赋值到产品对象上。
- Director类,构造函数会传入Builder对象,负责从用户端收集产品信息,Director将用户传入的产品属性信息传入到Builder对象。
四、适配器模式
定义:是原本两个不同类的对象,通过适配,变成能相互调用的模式。
例如,英国人只能讲英语,日本人只能说日语,如何让英国人说出日语呢?
适配器模式可分为对象适配器和类适配器两种,类适配器的适配器和适配者是 继承关系,而对象适配器则是引用关系。
下面用代码简单说明下适配器的写法:
1.对象适配器模式写法
注意:对象类型的适配者类型可以是接口或者是具体的类。
/**
* 英国人
*/
public class English {
void speakEnglish(String string){
System.out.println("英国人"+string);
}
}
/**
* 日本人
*/
public class Japanese {
void speakJapanese(String string){
System.out.println("日本人"+string);
}
}
/**
* 翻译角色的适配器
*/
public class Translator extends Japanese {//翻译官与日本人是继承关系
private English mEnglish = new English();
public Translator(English english){//翻译官需要引用英国人的对象
this.mEnglish = english;
}
@Override
public void speakJapanese(String str) {
mEnglish.speakEnglish(str);//翻译官在这里偷梁换柱
}
}
/**
* 客户端使用代码
*/
public class MyClass {
public static void main(String[] args) {
English english = new English();
Translator translator = new Translator(english);
translator.speakJapanese("说日语");
}
}
输出结果
英国人说日语
2.类适配器模式写法:
注意:类适配器模式的适配者只可以是接口。
/**
* 日本人接口
*/
public interface JapaneseInterface {
void speakJapanese(String string);
}
/**
* 翻译角色的适配器
*/
public class Translator2 extends English implements JapaneseInterface{
@Override
public void speakJapanese(String string) {//外面翻译官调用说日语的方法,实际调用说英语
speakEnglish(string);//直接在这里偷梁换柱
}
}
/**
* 客户端使用代码
*/
public class MyClass {
public static void main(String[] args) {
Translator2 translator2 = new Translator2();
translator2.speakJapanese("说日语");
}
}
输出结果:
英国人说日语