经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
GOF23--23种设计模式(一)
来源:cnblogs  作者:回忆也交给时间  时间:2023/12/5 12:09:08  对本文有异议

一.什么是设计模式

设计模式(Design  Pattern)是前辈们对代码开发经验的总结,是解决一系列特定问题的套路。

它不是语法规定,而是一套用来提高代码复用性,可读性,可维护性,稳健性,安全性的解决方案

设计模式的雏形:

1995年,GOF(Gang  of  Four,四人/四人帮)合作出版了《设计模式:可复用的面向对象软件的基础》一书,共收集了23种设计模式,从此有了设计模式的历程碑,人称【GoF设计模式】

设计模式的本质是面向对象设计的实际应用,是对类的封装,继承,多态,以及类的关联,和组合关系的充分理解

使用设计模式有以下优点:

  • 可以提高程序员的思维能力,编程和设计能力
  • 使得程序设计更加标准化,代码编制更容易加工,从而缩短软件开发周期
  • 使设计代码重用性高,可读性高,可靠性高,灵活性,可维护性强

OOP的七大原则:

开闭原则:对扩展开放,对修改关闭

  • 指的是在面向对象的设计中,当有新的需求时,不会优先改变源码,而是通过其它方式(继承,多态等)在源码的基础上拓展新功能

里氏替换原则:继承必须确保父类的所拥有的性质在子类依旧成立

  • 指的是在程序设计中,对于子类继承父类,子类中父类的属性和方法都能正常使用,子类需要新的需求就自己写,不要直接重写父类的方法,如果为了重写父类已有的方法而继承,对于程序的复用性会大打折扣

依赖倒置原则:面向接口编程,不要面向实现编程

  • 指的是在程序设计中,不应该力求于怎么实现这个功能,应该先思考有那些方法,各自负责什么,实现的细节交由实现类,抽象功能交给接口,更深层次就是面向抽象编程,不要面向实现编程

单一职责原则:控制类的粒度,将对象解耦,提高其内聚性

  • 指的是一个类就专注于实现好一个功能就行了,就像一个方法就实现一个细节一样,如用户登录,想要一个方法负责密码校对又负责检测用户名是否存在,就是一个方法干了多件事,可以把检测用户是否存在抽象为另一个方法,然后调用它,这样类的粒度就低了,粒度越高,代码越可能出现问题

接口隔离原则:为各个类建立它们专需的接口

  • 指的是在程序设计的时候,需要对一个接口对应一个或多个实现类,它们负责的模块可以很小,但是需要专一,不要多个功能都冗余在一个接口内部,应该实现专一功能,然后可以多个实现类来实现更小的细节

迪米特法则:只与你的朋友交谈,不和陌生人说话

  • 指的是两个类需要交流时应该通过一个中间类,不要让它们两个类直接交流,如用户登录时需要密码校对(A类的功能),校对前需要进行用户名检测是否村在(B类的功能),它们之间有耦合关系,但是程序设计中不能将B类塞到A类中,而是需要一个C类,将B类和A类组合,然后实现此功能,好处是,A类B类保持纯粹,坏处是多了一个开销C类

合成复用原则:尽量优先使用组合和聚合的方式实现类之间的关系,其次才考虑继承来实现

  • 指的是在类的关系中多用组合和聚合的方式设计类,组合优于继承,如果你只想使用父类的方法,而很少或根本不再设计新的方法属性,就肯定要使用组合,如果是需要大面积更改父类方法,或者重构父类,则使用继承

注意:OOP的七大原则,多用于设计阶段,需要分清设计和实现的区别

 二.工厂模式

实现了创建者和调度者的分离

原来的调度者即是创建者,类就在自己的项目中,且可看源码,所以要使用的时候可以直接new出来,这种方式创建对象需要自己十分的了解这个类,如需要哪些参数,清楚内部的实现细节

在大型项目的设计中,都是面向接口编程,对于调度者,它只知道此接口的内容,和有一些实现类,并不知道实现类的具体细节,如果自己创建对象,很大概率会被抽象接口给整蒙,所以工厂模式出现了,它用于实现对象的创建,创建对象的细节都由工厂模式解决(也就是架构师),普通开发者只用知道自己使用的实现类是那个工厂提供的,然后在工厂内拿取对象,不必自己创建,而只是利用工厂调度

详细工厂的分类:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

理论上,工厂模式满足:开闭原则,依赖倒转原则,迪米特法则

但是,实际工作中以效率和业务开发为主,不一定完全满足,这取决于效率和理论的冲突

工厂模式的核心本质:

实例化对象不在使用new关键字,用工厂代替

将选择实现类,创建实现类对象统一管理和控制,从而将调用者和实现类解耦

简单工厂模式

简单工厂模式也叫静态工厂模式,指的是工厂中的代码块都是写死的,动态的拓展类需要在工厂中新增代码块来完成对象的创建

接口,Animal:

  1. public interface Animal {
  2. void getName();
  3. }

 

通过此接口拓展出的实现类:

  • cat类
  1. public class Cat implements Animal{
  2. @Override
  3. public void getName() {
  4. System.out.println("猫类,实现Animal接口");
  5. }
  6. }

 

  • dog类
  1. public class Dog implements Animal{
  2. @Override
  3. public void getName() {
  4. System.out.println("狗类,实现Animal类");
  5. }
  6. }

 

普通的创建对象的方式,通过new关键字实现:

  1. //普通的创建对象方式
  2. Cat cat = new Cat();
  3. Dog dog = new Dog();

 

这种方式使用的前提是创建者对类的内部结构要熟悉,清楚需要什么参数才能创建对象,我们例子的实现类简单,肯定用new关键字很适用,但是这一期主要讲工厂模式,所以我们看看工厂模式怎么创建对象

简单构造一个简单工厂来创建对象(重理解):

  1. public class AnimalFactory {
  2. public static Animal getAnimal(String name){
  3. if (name.equals("cat")){
  4. return new Cat();
  5. } else if (name.equals("dog")) {
  6. return new Dog();
  7. }else {
  8. return null;
  9. }
  10. }
  11. }

 

如上就是普通工厂的写法,它是讲已有的类先写入工厂中,这就导致工厂的实现类被写死了,如果新增一个拓展类,就需要改变普通工厂的源码,这很显然不符合开闭原则

工厂模式拿取对象:

  1. //工厂模式创建对象
  2. Animal cat1 = AnimalFactory.getAnimal("cat");
  3. Animal dog1 = AnimalFactory.getAnimal("dog");

 

新建一个Mouse实现类:

  1. public class Mouse implements Animal {
  2. @Override
  3. public void getName() {
  4. System.out.println("老鼠类,实现于Animal类");
  5. }
  6. }

 

需要改变普通工厂模式的代码:

  1. public class AnimalFactory {
  2. public static Animal getAnimal(String name){
  3. if (name.equals("cat")){
  4. return new Cat();
  5. } else if (name.equals("dog")) {
  6. return new Dog();
  7. }else if (name.equals("mouse")) {
  8. //新增的拓展实现类
  9. return new Mouse();
  10. }else {
  11. return null;
  12. }
  13. }
  14. }

 

每次新增拓展类都需要改变普通工厂类的原因:普通工厂是拿取对象的必经之路,是和其它实现类的唯一联系

普通工厂模式生产对象略图:

 工厂方法模式

工厂方法模式支持实现类的横向拓展,它在普通工厂模式的基础上,增加工厂模式接口,对于每个实现类有专门的接口,

也就是说实现类,实现接口的具体细节,而工厂实现类实现的是工厂模式的创建对象

  • 优点是可以横向拓展业务,不需要改变已经有的工厂模式来融入
  • 缺点是代码量直接翻倍,冗余比较大

接口Animal:

  1. public interface Animal {
  2. void getName();
  3. }

 

Animal工厂接口:

  1. public interface AnimalFactory {
  2. Animal getAnimal();
  3. }

 

接口实现类:

  1. public class Cat implements Animal {
  2. @Override
  3. public void getName() {
  4. System.out.println("猫类,实现Animal接口");
  5. }
  6. }

 

工厂接口实现类:

  1. public class CatFactory implements AnimalFactory{
  2. @Override
  3. public Animal getAnimal() {
  4. return new Cat();
  5. }
  6. }

 

如上,每个实现类都有它专有的工厂实现类,使得每个实现类都是专门的工厂来加工的,它们各个工厂实现类都是独立存在的互相解耦,所以要创建对象现在就需要去找它们对应的工厂

这样构建工厂的好处是,横向的新增业务,如果现在新增一个业务只需要实现类实现Animal接口,它对应的工厂实现工厂接口,和其它工厂是独立存在的,不需要改变已有的工厂

能实现横向拓展的关键在于,接口和工厂接口都不是关键路径了,而是约束实现类的组成

工厂方法模式创建对象:

  1. //方法工厂模式拿取对象
  2. Animal cat = new CatFactory().getAnimal();
  3. Animal dog = new DogFactory().getAnimal();

 

工厂方法模式生产对象略图:

三.抽象工厂模式

抽象工厂模式也是工厂模式的一种,但是它的特点和普通工厂模式,工厂方法模式的机制都是不同的

抽象工厂模式围绕一个超级工厂,其它工厂的创建都是由这个超级工厂约束的

定义:抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类

优点:

  • 具体产品在应用层隔离,无需关心创建细节
  • 将一个系列的产品统一到一起实现

缺点:

  • 产品簇新增产品困难
  • 增加了系统抽象性和理解难度

产品接口:

phone

  1. //产品接口,具体的实现细节交给厂商
  2. public interface PhoneProduct {
  3. void getPhoneName();
  4. void getNumber();
  5. void getProduct();
  6. }

 

router

  1. //产品接口,具体的实现细节交给厂商
  2. public interface RouterProduct {
  3. void getRouterName();
  4. void getRouterNumber();
  5. void getRouterProduct();
  6. }

 

抽象工厂接口,工厂都需要实现此接口:

  1. //抽象工厂,所有的工厂都需要实现这个超级工厂
  2. public interface AbstractFactory {
  3. PhoneProduct phone();
  4. RouterProduct router();
  5. }

 

普通工厂:

XiaoMi:

  1. public class MiFactory implements AbstractFactory{
  2. @Override
  3. public PhoneProduct phone() {
  4. return new MiPhone();
  5. }
  6. @Override
  7. public RouterProduct router() {
  8. return new MiRouter();
  9. }
  10. }

 

HuaWei:

  1. public class HWFactory implements AbstractFactory{
  2. @Override
  3. public PhoneProduct phone() {
  4. return new HuaWeiPhone();
  5. }
  6. @Override
  7. public RouterProduct router() {
  8. return new HuaWeiRouter();
  9. }
  10. }

 

抽象工厂模式生产对象略图:

三种工厂模式总结

简单工厂模式:虽然某种程度上不符合设计模式,但是实际应用最多

工厂方法模式:不修改已有类的情况下,通过新增工厂实现类的拓展

抽象工厂模式:不可以新增产品,但是可以新增产品簇或者说,不建议修改已经写好的抽象工厂接口,但是实现抽象工厂接口的普通工厂可以横向拓展

四.单例模式

单例模式指的是在创建对象的时候,只允许全局存在一个对象,从而达到资源共享的目的

实现单例模式的方式一共有两种:

  • 饿汉式单例
  • 懒汉式单例

饿汉式单例

饿汉式单例的特点是将一个类的构造器私有化,不让外部的程序手动的创建对象

而这个类的对象则使用静态方法获取,由程序加载初始化的时候就开始创建,然后伴随程序的结束为止

  1. //饿汉式单例模式
  2. public class HungryInstance {
  3. //私有化构造器,不允许外部类任意创建对象
  4. private HungryInstance(){
  5. }
  6. //创建静态对象,在类初始化时就被创建对象
  7. private static HungryInstance hungry=new HungryInstance();
  8. //外部类利用方法拿取对象,不由外部类自主创建对象
  9. public static HungryInstance getHungry(){
  10. return hungry;
  11. }
  12. }

 

饿汉式单例模式有一个缺点,也就是此类的对象是静态的,它和程序加载顺序有关系,静态的代码块会和程序初始化一起加载,所以有可能此类如果所需空间很大但是使用不平凡,会白占很多空间

如我们此类需要申请一片内存空间:

  1. private String[] s1=new String[1000];
  2. private String[] s2=new String[1000];
  3. private String[] s3=new String[1000];
  4. private String[] s4=new String[1000];

 

如上,这片空间会在程序初始化就被占用,且一直存在到程序结束,如果这个单例本身使用很少,内存开销就很不合算

懒汉式单例

懒汉式单例也需要将构造器私有,避免外部类创建对象

懒汉式不是再使用静态属性来创建对象,而是通过方法调用,由方法创建

如果没使用此方法就并不会存在此对象,如果使用了此方法就创建一个对象

然后加一个检测机制,调用此方法时,如果对象存在就直接返回对象,避免创建,如果不存在,则当场创建一个

  1. //懒汉式单例
  2. public class LazyInstance {
  3. //私有化构造器,避免外部类创建对象
  4. private LazyInstance(){
  5. }
  6. private LazyInstance lazy;
  7. //y由调用方法创建对象,被调用才会被创建,没被调用对象就不存在
  8. public LazyInstance getLazy(){
  9. if (lazy==null){
  10. lazy = new LazyInstance();
  11. return lazy;
  12. }else {
  13. return lazy;
  14. }
  15. }
  16. }

 

懒汉式单例也有自己的一个问题,那就是多线程的情况下,检测机制太简单,单例会被破坏

原因是上面方法创建对象的操作不是原子性,创建对象的过程:1.分配内存空间,2.执行构造方法,初始化对象,3.把对象指向这个空间

创建对象的顺序是123,132都可能,如果多个线程同时来拿对象只有还没进行到第3步,都会默认没有对象,但实际情况是已经有线程正在创建了,所以就会导致多个线程创建了多个对象

解决方式,加锁(synchronized)

  1. //由调用方法创建对象,被调用才会被创建,没被调用对象就不存在
  2. public static LazyInstance getLazy() {
  3. if (lazy == null) {
  4. //加上线程同步机制,当对象不存在时将此类资源锁住
  5. synchronized (LazyInstance.class) {
  6. if (lazy == null) {
  7. lazy = new LazyInstance();
  8. return lazy;
  9. }
  10. }
  11. }
  12. return lazy;
  13. }

 

加上同步机制后,在创建对象时,会将类资源锁住,先获得锁的线程就就去创建对象,其它线程只能等待此线程释放锁

当对象创建完成后,其它线程先后获得锁,但是对象此时已经被最先拿到锁的线程创建了,所以其它线程都不能创建对象而是直接返回已经创建好的对象

静态内部类单例

这是使用了Java静态内部类的特点,它可以直接拿到外部类的静态资源,然后又不会直接被初始化加载,它和饿汉式有异曲同工之妙

饿汉式是在程序加载时就初始化一个对象出来,而它需要在被调用时才能拿到对象,由于创建对象的类中,又是final修饰,所以在调用方法的时候不会多创建对象

  1. //静态内部类
  2. public class StaticClass {
  3. //私有化构造器
  4. private StaticClass(){
  5. }
  6. //返回静态内部类的属性
  7. public static StaticClass getInstance(){
  8. return InnerClass.sc;
  9. }
  10. //静态内部类负责创建外部类的对象
  11. public static class InnerClass{
  12. private static final StaticClass sc = new StaticClass();
  13. }
  14. }

上面三种方式的缺点

只要有反射机制存在,以上三种方式创建对象都是不安全的

 反射机制使得私有的构造器依旧可以被拿到,反射机制面前就没有私有的属性了,我们可以使用反射机制来创建对象

  1. //通过反射拿取类的构造器
  2. Constructor<LazyInstance> lazy = LazyInstance.class.getDeclaredConstructor(null);
  3. //设置构造器的熟悉为可访问
  4. lazy.setAccessible(true);
  5. //通过反射拿取构造器创建对象
  6. LazyInstance lazy1 = lazy.newInstance();
  7. LazyInstance lazy2 = lazy.newInstance();
  8. //展示hashcode
  9. System.out.println(lazy1);//LazyInstance@4554617c
  10. System.out.println(lazy2);//LazyInstance@74a14482

 

如上,通过反射机制将构造器再次变为公有属性以后,已经可以通过外部类继续创建对象

所以这种基于类的单例模式大多都是不安全的,关键在于Java的反射机制使得构造器无法真正的私有化

但是如果有能拒绝反射机制的方式,阁下又如何应对呢?接下来的枚举类值得一看

枚举类单例

枚举类的特点:

枚举类的构造器都是私有的(无论是否显式表达,都是私有的),因此枚举类不能对外创建对象

can't deserialize enum" :不能通过反射拿取枚举类
枚举类直接拒绝反射机制,从根本上杜绝了反射更改构造器属性为公有的情况
  1. public enum EnumInstance {
  2. //实例对象
  3. Instance;
  4. //私有构造器,不管是否显示私有化都是私有的,改为公有编译错误
  5. private EnumInstance(){
  6. }
  7. //拿取对象实例方法
  8. public EnumInstance getInstance(){
  9. return Instance;
  10. }
  11. }

 试试用反射取改变构造器属性为公有

  1. //枚举的构造器不是无参构造,Idea和JavaP命令都反编译为无参构造,而真正的构造器为参数为String和int
  2. Constructor<EnumInstance> ei = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
  3. //设置构造器为公共属性
  4. ei.setAccessible(true);
  5. //通过构造器创建对象
  6. EnumInstance e1 = ei.newInstance();
  7. EnumInstance e2 = ei.newInstance();
  8. //展示hashcode
  9. System.out.println(e1);
  10. System.out.println(e2);

指向如上代码报错:

 意思是不能使用反射创建枚举对象

 

 

 

原文链接:https://www.cnblogs.com/5ran2yl/p/17871441.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号