Java基础-设计模式详解
一、设计模式的分类
总体来说设计模式分为三大类:
<1> 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
<2> 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
<3> 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式。
二、设计模式的六大原则
<1> 开闭原则--Open Close Principle
开闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。意思就是:为了使程序的扩展性好,易于维护和升级。
<2> 里氏代换原则--Liskov Substitution Principle
里氏代换原则(Liskov Substitution Principle LSP)面向对象涉及的基本原则之一。
任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
<3> 依赖倒转原则--Dependence Inversion Principle
依赖倒转原则是开闭原则的基础,具体解释为:针对接口编程,依赖于抽象而不依赖于具体。
<4> 接口隔离原则--Interface Segregation Principle
此原则的意思是:使用多个隔离的接口比使用单个接口要好。能够降低类之间的耦合度。从这里我们就能看出设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便:降低依赖,降低耦合。
<5> 迪米特法则(最少知道原则)--Demeter Principle
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
<6> 合成复用原则--Composite Reuse Principle
尽量是用合成/聚合的方式,而不是使用继承。
Java中的23种设计模式
1、工厂模式--Factory Method
工厂模式分为三种:
<1> 普通工厂模式
建立一个工厂类,对实现了同一接口的一些类进行实例的创建,如图:
例子:发送邮件和短信
首先,创建一个共同的接口
public interface Sender {
public void Send();
}
其次,创建发送短信和邮件的实现类
public class MailSender implements Sender {
@Override public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override public void Send() {
System.out.println("this is sms sender!");
}
}
最后创建工厂类:
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
测试类:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
输出: this is sms sender!
<2> 多个工厂方法模式
该模式是对普通工厂方法模式的改进,在扑通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
将普通工厂模式的SendFactory 进行修改
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
测试类:
public class FactoryTest{
public static void main(String[] args){
SendFactory factory = new SendFactory();
Sender sender =factory.produceMail();
sender.Send();
}
}
输出: this is mailsender!
<3> 静态工厂方法模式
将多个工厂方法模式中的方法置为静态的,不需要创建实例,直接调用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
测试类:
public class FactoryTest{
public static void main(String[] args){
Sender sender = SenderFactory.produceMail();
sender.Send();
}
}
输出:this is mailsender!
总体来说:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第二种解决了此问题,但需进行实例化。第三种相比第二种就不需要实例化工厂类。因此大多情况下会选用第三种---静态工厂方法模式。
2、抽象工厂模式--Abstract Factory
举例:
接口:
public interface Sender {
public void Send();
}
两个实现类:
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("this is mailsender!");
}
}
public class SmsSneder implements Sender{
@Override
public void Send(){
System.out.println("this is sms sender!")
}
}
通用接口:
public interface Provider{
public Sender produce();
}
两个工厂类:
public class SenderMailFactory implements Provider{
@Override
public Sender produce(){
return new MailSender();
}
}
public class SendSmsFactory implements Provider{
@Override
public Sender produce(){
return new SmsSender();
}
}
测试类:
public class Test{
public static void main(String[] args){
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
此模式的好处是:如果现在想增加一个功能,只需再写一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口就可以了,无需改动现有代码。
3、单例模式--Singleton
单例模式是设计模式中最常见也是最简单的一种设计模式,保证了在程序中只有一个实例存在并且能全局访问到。
适用场景:
--应用中某个实例对象需要频繁的被访问
--应用中每次启动只会存在一个势力。如账号系统、数据库系统。
常用的使用方式
<1> 懒汉式:
优点:延迟加载(需要的时候采取加载)
缺点:线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行的频繁读写操作时。
具体实现:
public class Singleton{
/*持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载*/
private static Singleton instance = null;
/*私有构造方法,防止被实例化*/
private Singleton(){};
/*1:懒汉式,静态方法,创建势力*/
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
<2> 加同步锁
优点:解决了线程不安全的问题
缺点:效率低,每次调用实例都要判断同步锁
pubic static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
另一种写法:
/*加上synchronized,但是每次滴啊用实力都会加载*/
public static Singleton getInstance(){
synchronized(Singleton.class){
if(nstance == null){
instance = new Singleton();
}
}
return instance;
}
<3> 双重检验锁
要优化(2)中因为每次调用实例都要判断同步锁的问题,很多人都使用下面的一种双重判断校验的办法。
优点:在并发量不多,安全性不高的情况下或许能很完美运行单例模式
缺点:不同平台编译过程中可能会存在严重安全隐患。
/*3.双重锁定:只在第一次初始化的时候加上同步锁*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
这种方法貌似很完美的解决了上述效率问题,在并发量不多,安全性不太高的情况下或许能完美运行。但是,这种方法也有问题,出现在这句:
instance = newSingleton();
在JVM编译的过程中会出现指令重排的优化过程,这就会导致当instance实际上还没初始化,就可能被分配了内存空间,也就是会出现instance!=null,但是又没初始化的情况,这样就会导致返回的instance不完整。
<4> 内部类的实现
优点:延迟加载,线程安全(Java中class加载时互斥的),减少了内存消耗。内部类是一种好的实现方式。
public class SingletonInner{
private static class SingletonHolder{
private static SingletonInner instance = new SingletonInner();
}
/*私有构造函数*/
private SingletonInner(){}
public static SingletonInner getInstance(){
return SingletonHolder.instance;
}
protected void method(){
System.out.println("SingletonInner");
}
}
<5> 枚举
public enum SingletonEnum {
/** *
1.从Java1.5开始支持;
* 2.无偿提供序列化机制;
* 3.绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候;
*/
instance;
private String others;
SingletonEnum() {
}
public void method() {
System.out.println("SingletonEnum");
}
public String getOthers() {
return others;
}
public void setOthers(String others) {
this.others = others;
}
}
文章整理于知乎java后端开发(链接地址:https://zhuanlan.zhihu.com/p/93770973)