设计模式
设计模式是一套在软件设计中常用的解决问题的经验总结和最佳实践。它们是针对常见的软件设计问题,经过反复验证和实践后形成的可复用、灵活和高效的解决方案。设计模式不是一种具体的编程语言特性,而是一种通用的设计思想,可以在各种编程语言和开发环境中应用。
设计模式的目标是帮助开发者解决常见的设计问题,如对象创建、对象间的关系、代码结构和逻辑组织等。使用设计模式可以使代码更易于理解、维护和扩展,并且提高代码的质量和可重用性。
理解设计模式的关键在于:
-
理解设计模式的目的:每种设计模式都有特定的目的和使用场景,了解设计模式的用途是理解它的基础。
-
学习常见的设计模式:学习和熟悉常见的设计模式,如单例模式、工厂模式、观察者模式、策略模式等。这些设计模式在实际应用中使用非常广泛。
-
了解设计模式的结构:了解每种设计模式的结构、角色和交互方式,理解设计模式是如何工作的。
-
实践和应用:将学习到的设计模式应用到实际项目中,通过实践掌握设计模式的使用技巧和注意事项。
-
灵活运用:设计模式并不是一成不变的,可以根据实际情况和需求进行适当的调整和变化。
设计模式是软件开发中的一个重要组成部分,它们能够帮助开发者解决常见的设计问题,提高代码的质量和可维护性。通过学习和运用设计模式,开发者可以更加高效地编写出结构良好、可扩展的代码,并且更好地应对软件开发中的挑战。
设计模式一共有23种,这23种设计模式的本质是面向对象设计原则的实际应用,是对类的封装性,继承性和多态性以及类的关联关系和组合关系的充分理解。
设计模式可以按照其目的和作用进行分类,通常可以分为以下几类:
-
创建型模式(Creational Patterns)5种:主要关注对象的创建过程,隐藏对象的创建细节,提供一种通用的创建方法。常见的创建型模式包括:
-
单例模式(Singleton Pattern)
-
工厂模式(Factory Pattern)
-
抽象工厂模式(Abstract Factory Pattern)
-
建造者模式(Builder Pattern)
-
原型模式(Prototype Pattern)
-
-
结构型模式(Structural Patterns)7种:主要关注对象的组合和结构,如何将类和对象组合成更大的结构,以便实现更复杂的功能。常见的结构型模式包括:
-
适配器模式(Adapter Pattern)
-
桥接模式(Bridge Pattern)
-
装饰器模式(Decorator Pattern)
-
外观模式(Facade Pattern)
-
享元模式(Flyweight Pattern)
-
代理模式(Proxy Pattern)
-
组合模式(Composite Pattern)
-
-
行为型模式(Behavioral Patterns)11种:主要关注对象之间的通信和协作,解决对象间的交互问题。常见的行为型模式包括:
-
观察者模式(Observer Pattern)
-
模板方法模式(Template Method Pattern)
-
策略模式(Strategy Pattern)
-
命令模式(Command Pattern)
-
职责链模式(Chain of Responsibility Pattern)
-
迭代器模式(Iterator Pattern)
-
中介者模式(Mediator Pattern)
-
备忘录模式(Memento Pattern)
-
解释器模式(Interpreter Pattern)
-
状态模式(State Pattern)
-
访问者模式(Visitor Pattern)
-
设计原则:
设计模式的六大原则是在软件设计中的一些通用准则,它们可以帮助开发者更好地设计出灵活、可扩展、可维护的软件系统。这些原则是设计模式的基础,也是优秀的软件设计的基石。
-
开闭原则(Open/Closed Principle,OCP):软件实体应该对扩展开放,对修改关闭。即一个软件实体(类、模块等)应该通过扩展来实现新的功能,而不是通过修改已有代码来实现,想要达到这种效果我们需要使用接口和抽象类。
-
组合聚合原则(CRP):尽量使用对象组合,而不是通过继承达到服用的目的。
-
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因。即一个类只负责一项职责,尽量保持类的职责单一,不要让一个类承担过多的功能。
-
里氏替换原则(Liskov Substitution Principle,LSP):子类应该能够替换其父类并出现在其父类能够出现的任何地方,而不会影响程序的正确性。即子类应该保持父类的行为和约束。
-
接口隔离原则(Interface Segregation Principle,ISP):一个类不应该强迫它的客户端依赖于它不需要的方法。即一个类不应该实现不属于它的接口。实现方式就是使用多个专门的接口,而不使用单一的总接口。
-
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,而是应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
-
迪米特法则(Law of Demeter,LoD):一个对象应该对其他对象有最少的了解。即一个对象应该尽可能地减少对其他对象的依赖,降低耦合性。比如:IOC。
这些设计原则是软件设计的重要指导原则,可以帮助开发者编写出易于维护、可扩展和灵活的代码。在实际软件设计过程中,结合这些原则,采用合适的设计模式,可以更好地组织代码,提高软件质量和可维护性。
单例模式:
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
-
单例模式(Singleton):
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
在当前系统中,某个类型的对象,最多只能有一个,就需要使用单例设计模式。
-
单例模式的设计原则: 1、构造方法私有化 2、在类中创建好该类对象 3、在类中,给外界提供获取该对象的公有方式
没有使用单例
java
/**
* 没有使用单例
*/
public class Simple01 {
public static void main(String[] args) {
Singleton s1 = new Singleton();
Singleton s2 = new Singleton();
if (s1 == s2) {
System.out.println("两个对象是相同的实例");
} else {
System.out.println("两个对象是不相同的实例");
}
}
}
class Singleton {
}
饿汉式
-
在加载类的同时,就要初始化静态成员变量,所以就同时将该类对象创建出来了
-
饿汉式:一有机会,马上就吃,不去等待。(一旦加载类型,马上创建对象)
-
线程安全
代码示例:
java
/**
* 饿汉式
*/
public class Simple02 {
public static void main(String[] args) {
SingletonHungry s1 = SingletonHungry.getInstance();
SingletonHungry s2 = SingletonHungry.getInstance();
if (s1 == s2) {
System.out.println("两个对象是相同的实例");
} else {
System.out.println("两个对象是不相同的实例");
}
}
}
class SingletonHungry {
//私有化构造方法
private SingletonHungry() {
}
//在类中声明该类型的引用,并且创建对象
private static SingletonHungry sh = new SingletonHungry();
//给外界提供获取对象的方式
public static SingletonHungry getInstance() {
return sh;
}
}
**优点:**执行效率高,使用前就加载了,性能高,没有加任何锁,代码结果简单
**缺点:**类加载就初始化了,某些情况会造成内存浪费(类被加载到内存,但是没有使用)
**总结:**饿汉式是空间换时间,当类被加载的时候就会创建类实例,不管你用不用,先创建出来,
然后每次调用的时候,就不需要再判断了,节省了运行时间。
线程是安全的
测试代码
java
public class Test {
public static void main(String[] args) {
new Thread(new MyTask(),"线程1").start();
new Thread(new MyTask(),"线程2").start();
}
}
class MyTask extends Thread{
@Override
public void run() {
SingletonHungry instance = SingletonHungry.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance);
}
}
懒汉式
-
在加载类的时候,不同时创建该类对象,等到需要获取这个对象时,才去创建这个对象
-
懒汉式:不着急、能不创建的时候,就不创建,能拖就拖
-
线程不安全
-
注意事项:
-
只有在sl == null的时候,才会创建对象
-
sl的判断和sl的赋值,不希望分离开,否则在多线程环境下,会出现多个对象的状态,所以sl的判断和sl的赋值,需要放到一个同步代码块中。
-
同步代码块的效率非常低,不是每次获取对象的时候,都需要判断锁对象,而是在sl为null的时候, 才应该判断锁对象,因此在外层需要嵌套一个if判断,判断sl是否为null
-
代码示例:
java
/**
* 懒汉式
*/
public class Simple03 {
public static void main(String[] args) {
SingletonLazy instance1 = SingletonLazy.getInstance();
SingletonLazy instance2 = SingletonLazy.getInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
public class SingletonLazy {
private static SingletonLazy singletonLazy;
private SingletonLazy() {
}
public static SingletonLazy getInstance () {
if (singletonLazy == null ) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
public class Test {
public static void main(String[] args) {
new Thread(new MyTask(),"线程1").start();
new Thread(new MyTask(),"线程2").start();
}
}
class MyTask extends Thread{
@Override
public void run() {
SingletonLazy instance = SingletonLazy.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance);
}
}
/**
* 懒汉式同步代码块
*/
public class SingletonLazy {
private static SingletonLazy singletonLazy;
//构造方法私有化
private SingletonLazy() {
}
//给外界提供获取对象的方式
public static SingletonLazy getInstance() {
synchronized (SingletonLazy.class) {
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
}
**优点:**节省内存空间,解决了内存浪费问题
**缺点:**线程不安全,有可能会创建两个实例,加锁,性能低(一万个线程排队访问指定资源)
**总结:**懒汉式是时间换空间,每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。
当然,如果一直调用的话,就不会创建实例,则节约内存空间。
总结
单例模式应用场景
单例模式优点
-
在内存中只有一个实例,减少内存开销
-
可以避免对资源的多重占用
-
设置全局访问点,严格控制访问
单例模式缺点
-
没有接口,拓展困难
-
如果要扩展单例对象,只有修改代码,没有其它途径
单例设计模式知识重点总结
-
私有构造器
-
保证线程安全
-
延迟加载
-
防止序列化和反序列化破坏单例
-
防御反射攻击单例
常见应用场景
项目中用于读取配置文件的类。
数据库连接池。因为数据库连接池是一种数据库资源。
Windows中任务管理器,回收站。