概览
Java的设计模式大体上分为三大类,共23种:
- 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
- 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
- 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式遵循的原则有6个:
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
2、里氏代换原则(Liskov Substitution Principle)
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的借口来降低耦合度。
5、迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
介绍一些常见、常用的设计模式。
常用的设计模式
单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
为什么使用单例模式
当我们需要确保某个类只要一个对象,或创建一个类需要消耗的资源过多,如访问IO和数据库操作等,这时就需要考虑使用单例模式了。
比如:当我们使用多线程操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行
数据库连接实例主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为用单例模式来维护,就可以大大降低这种损耗。
手写
懒汉式双重校验
保证线程安全,保证性能。 注意要使用volatile修饰单例,否则会出现重排序问题。volatile会让singleton每次从主存中读取数据,会降低一点点效率。
public class Singleton {
private static volatile Singleton singleton;
public static Singleton getSingleton(){
if( singleton==null ){
synchronized (Singleton.class){
if( singleton==null ){
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类加载
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当StaticSingleton第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
性能、线程安全兼顾。 相比双重校验式有一个缺点,就是不能在构造方法中加入参数。
public class StaticSingleton {
private StaticSingleton(){
}
private static class SingletonHoler {
private static final StaticSingleton INSTANCE = new StaticSingleton();
}
public static StaticSingleton getInstance(){
return SingletonHoler.INSTANCE;
}
}
枚举单例
此方法是相比之下最好的,枚举类实例创建是线程安全的,并且和普通类一样,拥有自己的字段和方法。
public enum SingletonEnum {
INSTANCE;
private String name = "makersy";
//example method
public void method(){
System.out.println("This is a SingletonEnum");
}
//getter
public String getName(){
return name;
}
}
观察者模式
一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。
观察者模式基于对象的状态变化和观察者通讯,以便他们作出相应的操作。简单的例子就是一个天气系统,当天气变化时必须在展示给公众的视图中进行反映。这个视图对象是一个主体,而不同的视图是观察者。
适配器模式
策略模式
概念: 策略模式可以定义一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。
使用场景
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为
2、一个系统需要动态地在几种算法中选择一种。
//策略接口
public interface Strategy {
public int doFor(int num1, int num2);
}
//策略1:加法
public class OperationAdd implements Strategy {
@Override
public int doFor(int num1, int num2) {
return num1+num2;
}
}
//策略2:减法
public class OperationDel implements Strategy{
@Override
public int doFor(int num1, int num2) {
return num1-num2;
}
}
//选用策略。传入不同策略返回不同值
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doFor(num1, num2);
}
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println(context.executeStrategy(4,2));
context = new Context(new OperationDel());
System.out.println(context.executeStrategy(4,2));
System.out.println(Runtime.getRuntime());
}
}
装饰者模式
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。
装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是Buffered 系列类如BufferedReader和BufferedWriter,它们增强了Reader和Writer对象,以实现提升性能的 Buffer 层次的读取和写入。
工厂模式
工厂方法模式:工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。这样做,使得需求组件的类不用与组件硬编码耦合,只用与生成这种对象实例的工厂进行耦合即可。
在Spring IoC中就经常用到工厂模式。
工厂模式分三种:简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式
并不是实际意义的工厂模式,只是简单抽象模式的一种情形。实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
这样做的优点:我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。如果,没有工厂的话,那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。
但是缺点也很明显:如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
工厂方法模式
工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。抽象工厂是可以生产多个产品的。
这里可以用反射机制来实现抽象工厂模式,这样做当添加很多子类的时候,可以不用在去更改工厂类,可以直接用反射来生成对象实例。
下面是一个例子,有一个person接口,另有两个实现类teacher和student,factory类传入对应类路径可以返回任意一个实例对象。
//person接口
public interface Person {
void speak();
}
//student类
public class Student implements Person{
@Override
public void speak() {
System.out.println("I'm a Student.");
}
}
//teacher类
public class Teacher implements Person {
@Override
public void speak() {
System.out.println("I'm a teacher.");
}
}
//工厂类
public class Factory {
public static Person getInstance(String className) {
Person p = null;
if( className!=null ){
try {
//Java9之后不建议使用这一方式
// p = (Person) Class.forName(className).newInstance();
p = (Person) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
e.printStackTrace();
}
}
return p;
}
public static void main(String[] args) {
Person p = Factory.getInstance("SheJiMoShi.factory.Teacher");
if( p!=null ){
p.speak();
}
}
}
这里还有一点不好,就是每次创建新对象都要改动代码。解决这一点,可以采用在配置文件中配置子类名,在代码中查询配置文件即可。
… …
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/19381.html