|
| 1 | + |
| 2 | + |
| 3 | +有一些重要的设计原则在开篇和大家分享下,这些原则将贯通全文: |
| 4 | + |
| 5 | +1. 面向接口编程,而不是面向实现。这个很重要,也是优雅的、可扩展的代码的第一步,这就不需要多说了吧。 |
| 6 | +2. 职责单一原则。每个类都应该只有一个单一的功能,并且该功能应该由这个类完全封装起来。 |
| 7 | +3. 对修改关闭,对扩展开放。对修改关闭是说,我们辛辛苦苦加班写出来的代码,该实现的功能和该修复的 bug 都完成了,别人可不能说改就改;对扩展开放就比较好理解了,也就是说在我们写好的代码基础上,很容易实现扩展。 |
| 8 | + |
| 9 | +**创建型模式比较简单,但是会比较没有意思,结构型和行为型比较有意思。** |
| 10 | + |
| 11 | +**每个代理模式的代码都必须自己手动完成一遍。** |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +## 创建型模式 |
| 16 | + |
| 17 | +创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,我们需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候。 |
| 18 | + |
| 19 | +### 简单工厂模式 |
| 20 | + |
| 21 | +```java |
| 22 | +public class FoodFactory { |
| 23 | + |
| 24 | + public static Food makeFood(String name) { |
| 25 | + if (name.equals("兰州拉面")) { |
| 26 | + Food noodle = new LanZhouNoodle(); |
| 27 | + System.out.println("兰州拉面"+noodle+"出锅啦"); |
| 28 | + return noodle; |
| 29 | + } else if (name.equals("黄焖鸡")) { |
| 30 | + Food chicken = new HuangMenChicken(); |
| 31 | + System.out.println("黄焖鸡"+ chicken +"出锅啦"); |
| 32 | + return chicken; |
| 33 | + } else { |
| 34 | + System.out.println("不知道你做的什么哦~"); |
| 35 | + return null; |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | + *其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。* |
| 42 | + |
| 43 | +```java |
| 44 | +public class Cook { |
| 45 | + public static void main(String[] args) { |
| 46 | + Food food = FoodFactory.makeFood("黄焖鸡"); |
| 47 | + FoodFactory.makeFood("jaja"); |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。 |
| 53 | + |
| 54 | +> 我们强调**职责单一**原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。 |
| 55 | +
|
| 56 | +在此例中可以看出,Cook 类在使用 FoodFactory 时就不需要 new 任何一个对象,这就是简单工厂模式的好处,封装了 new 的部分,做到的代码易用性。 |
| 57 | + |
| 58 | +### 工厂模式 |
| 59 | + |
| 60 | +简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。 |
| 61 | + |
| 62 | +```java |
| 63 | +public interface FoodFactory { |
| 64 | + Food makeFood(String name); |
| 65 | +} |
| 66 | +public class ChineseFoodFactory implements FoodFactory { |
| 67 | + |
| 68 | + @Override |
| 69 | + public Food makeFood(String name) { |
| 70 | + if (name.equals("A")) { |
| 71 | + return new ChineseFoodA(); |
| 72 | + } else if (name.equals("B")) { |
| 73 | + return new ChineseFoodB(); |
| 74 | + } else { |
| 75 | + return null; |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | +public class AmericanFoodFactory implements FoodFactory { |
| 80 | + |
| 81 | + @Override |
| 82 | + public Food makeFood(String name) { |
| 83 | + if (name.equals("A")) { |
| 84 | + return new AmericanFoodA(); |
| 85 | + } else if (name.equals("B")) { |
| 86 | + return new AmericanFoodB(); |
| 87 | + } else { |
| 88 | + return null; |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。 |
| 95 | + |
| 96 | +客户端调用: |
| 97 | + |
| 98 | +```java |
| 99 | +public class APP { |
| 100 | + public static void main(String[] args) { |
| 101 | + // 先选择一个具体的工厂 |
| 102 | + FoodFactory factory = new ChineseFoodFactory(); |
| 103 | + // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象 |
| 104 | + Food food = factory.makeFood("A"); |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +虽然都是调用 makeFood("A") 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。 |
| 110 | + |
| 111 | +第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。 |
| 112 | + |
| 113 | +**核心在于,我们需要在第一步选好我们需要的工厂**。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。 |
| 114 | + |
| 115 | +### 抽象工厂模式 |
| 116 | + |
| 117 | +当涉及到**产品族**的时候,就需要引入抽象工厂模式了。 一个经典的例子是造一台电脑 。 |
| 118 | + |
| 119 | +当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。 |
| 120 | + |
| 121 | +当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了**对修改关闭,对扩展开放**这个设计原则。 |
| 122 | + |
| 123 | +### 单例模式 |
| 124 | + |
| 125 | +简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。 |
| 126 | + |
| 127 | +getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。 |
| 128 | + |
| 129 | +**懒汉式写法(线程安全)** |
| 130 | + |
| 131 | +```java |
| 132 | +public class Singleton { |
| 133 | + private static Singleton instance; |
| 134 | + private Singleton (){} |
| 135 | + public static synchronized Singleton getInstance() { |
| 136 | + if (instance == null) { |
| 137 | + instance = new Singleton(); |
| 138 | + } |
| 139 | + return instance; |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +**饿汉式写法** |
| 145 | + |
| 146 | +```java |
| 147 | +public class Singleton { |
| 148 | + private static Singleton instance = new Singleton(); |
| 149 | + private Singleton (){} |
| 150 | + public static Singleton getInstance() { |
| 151 | + return instance; |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | + 弊端:因为类加载的时候就会创建对象,所以有的时候还不需要使用对象,就会创建对象,造成内存的浪费; |
| 157 | + |
| 158 | +**饱汉模式最容易出错:** |
| 159 | + |
| 160 | +```java |
| 161 | +public class Singleton { |
| 162 | + // 首先,也是先堵死 new Singleton() 这条路 |
| 163 | + private Singleton() {} |
| 164 | + // 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的 |
| 165 | + private static volatile Singleton instance = null; |
| 166 | + |
| 167 | + public static Singleton getInstance() { |
| 168 | + if (instance == null) { |
| 169 | + // 加锁 |
| 170 | + synchronized (Singleton.class) { |
| 171 | + // 这一次判断也是必须的,不然会有并发问题 |
| 172 | + if (instance == null) { |
| 173 | + instance = new Singleton(); |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | + return instance; |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +> 双重检查,指的是两次检查 instance 是否为 null。 |
| 183 | +> |
| 184 | +> volatile 在这里是需要的,希望能引起读者的关注。 |
| 185 | +> |
| 186 | +> 很多人不知道怎么写,直接就在 getInstance() 方法签名上加上 synchronized,这就不多说了,性能太差。 |
| 187 | +
|
| 188 | +嵌套类最经典,以后大家就用它吧: |
| 189 | + |
| 190 | +```java |
| 191 | +public class Singleton { |
| 192 | + |
| 193 | + private Singleton() {} |
| 194 | + // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性 |
| 195 | + private static class Holder { |
| 196 | + private static Singleton instance = new Singleton(); |
| 197 | + } |
| 198 | + public static Singleton getInstance() { |
| 199 | + return Holder.instance; |
| 200 | + } |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +> 注意,很多人都会把这个**嵌套类**说成是**静态内部类**,严格地说,内部类和嵌套类是不一样的,它们能访问的外部类权限也是不一样的。 |
| 205 | +
|
| 206 | +最后,我们说一下枚举,枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。 |
| 207 | + |
| 208 | +**TODO:** |
| 209 | + |
| 210 | +建造者模式 |
| 211 | + |
| 212 | +原型模式 |
| 213 | + |
| 214 | +## 结构型模式 |
| 215 | + |
| 216 | +前面创建型模式介绍了创建对象的一些设计模式,这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。 |
| 217 | + |
| 218 | + |
| 219 | + |
| 220 | +### 代理模式 |
| 221 | + |
| 222 | +第一个要介绍的代理模式是最常使用的模式之一了,用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。 |
| 223 | + |
| 224 | +既然说是**代理**,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。 |
| 225 | + |
| 226 | +理解**代理**这个词,这个模式其实就简单了。 下面上代码理解。 |
| 227 | +代理接口: |
| 228 | + |
| 229 | +```java |
| 230 | +//要有一个代理接口让实现类和代理实现类来实现。 |
| 231 | +public interface FoodService { |
| 232 | + Food makeChicken(); |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +被代理的实现类: |
| 237 | + |
| 238 | +```java |
| 239 | +public class FoodServiceImpl implements FoodService { |
| 240 | + @Override |
| 241 | + public Food makeChicken() { |
| 242 | + Food f = new Chicken(); |
| 243 | + f.setChicken("1kg"); |
| 244 | + f.setSpicy("1g"); |
| 245 | + f.setSalt("3g"); |
| 246 | + System.out.println("鸡肉加好佐料了"); |
| 247 | + return f; |
| 248 | + } |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +被代理实现类就只需要做自己该做的事情就好了,不需要管别的。 |
| 253 | + |
| 254 | +代理实现类: |
| 255 | + |
| 256 | +```java |
| 257 | +public class FoodServiceProxy implements FoodService { |
| 258 | + // 内部一定要有一个真实的实现类,当然也可以通过构造方法注入 |
| 259 | + private FoodService foodService = new FoodServiceImpl(); |
| 260 | + |
| 261 | + @Override |
| 262 | + public Food makeChicken() { |
| 263 | + System.out.println("开始制作鸡肉"); |
| 264 | + |
| 265 | + // 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的, |
| 266 | + // 代理只是在核心代码前后做些“无足轻重”的事情 |
| 267 | + Food food = foodService.makeChicken(); |
| 268 | + |
| 269 | + System.out.println("鸡肉制作完成啦,加点胡椒粉"); |
| 270 | + food.addCondiment("pepper"); |
| 271 | + System.out.println("上锅咯"); |
| 272 | + return food; |
| 273 | + } |
| 274 | +} |
| 275 | +``` |
| 276 | + |
| 277 | + 客户端调用,注意,我们要用代理来实例化接口: |
| 278 | + |
| 279 | +```java |
| 280 | +// 这里用代理类来实例化 |
| 281 | +FoodService foodService = new FoodServiceProxy(); |
| 282 | +foodService.makeChicken(); |
| 283 | +``` |
| 284 | + |
| 285 | +所谓代理模式,就是对被代理方法包装或者叫增强, 在面向切面编程(AOP)中,其实就是动态代理的过程。比如 Spring 中,我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。 |
| 286 | + |
| 287 | + |
| 288 | + |
| 289 | +待续。。。 |
0 commit comments