1.前言:
近期有很多面试者被问到:“有了解设计模式吗?”;”你了解到的设计模式有哪些?”;“在实际的开发中怎么使用设计模式进行开发?”。相信大家被面试官问道这种问题一定是很头痛的,回想自己在写JAVA的一路上,一定会在面试官背后愤怒的骂道“什么设计模式?这玩意听都没听说过,都没有用过,怎么了解?”,然后回来就自己偷偷地学习。
? 这种好事肯定会跟大家分享啦!既然说到这了,我也不卖关子了。这里就带领到大家了解什么设计模式。
2.什么是设计模式?
? 在设计模式的百度百科他是这样说的:设计模式是指控件在页面设计器中呈现时运行的代码。设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新开发者更加容易理解其设计思路。
下面举一个例子体会设计模式的精彩
一起打豆豆:
有个记者去南极采访一群企鹅,他问第一只企鹅:“你每天都干什么?”
企鹅说:“吃饭,睡觉,打豆豆!”
接着又问第 2 只企鹅,那只企鹅还是说:“吃饭,睡觉,打豆豆!”
记者带着困惑问其他的企鹅,答案都一样,就这样一直问了 99 只企鹅。
当走到第 100 只小企鹅旁边时,记者走过去问它:每天都做些什么啊?
那只小企鹅回答:"吃饭,睡觉."
记者惊奇的又问:"你怎么不打豆豆?"
小企鹅撇着嘴巴,瞪了记者一眼说:"我就是豆豆!"
楼哥,你搞错了吧,这是篇技术文,你咋讲笑话了?甭着急,继续往后面看哈~~
最LOW的方式:
假如现在有 3 只企鹅,都喜欢 “吃饭,睡觉,打豆豆”:
public class littlePenguin {
public void everyDay() {
System.out.println("吃饭");
System.out.println("睡觉");
System.out.println("用小翅膀打豆豆");
}
}
public class middlePenguin {
public void everyDay() {
System.out.println("吃饭");
System.out.println("睡觉");
System.out.println("用圆圆的肚子打豆豆");
}
}
public class bigPenguin {
public void everyDay() {
System.out.println("吃饭");
System.out.println("睡觉");
System.out.println("拿鸡毛掸子打豆豆");
}
}
public class test {
public static void main(String[] args) {
System.out.println("littlePenguin:");
littlePenguin penguin_1 = new littlePenguin();
penguin_1.everyDay();
System.out.println("middlePenguin:");
middlePenguin penguin_2 = new middlePenguin();
penguin_2.everyDay();
System.out.println("bigPenguin:");
bigPenguin penguin_3 = new bigPenguin();
penguin_3.everyDay();
}
}
看一下执行结果:
littlePenguin:
吃饭
睡觉
用小翅膀打豆豆
middlePenguin:
吃饭
睡觉
用圆圆的肚子打豆豆
bigPenguin:
吃饭
睡觉
拿鸡毛掸子打豆豆
这种方式是大家写代码时,最容易使用的方式,上手简单,也容易理解,目前看项目中陈旧的代码,经常能找到它们的影子,下面我们看怎么一步步将其进行重构
常规方式
“吃饭,睡觉,打豆豆” 其实都是独立的行为,为了不相互影响,我们可以通过函数简单进行封装:
public class littlePenguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public void beating() {
System.out.println("用小翅膀打豆豆");
}
}
public class middlePenguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public void beating() {
System.out.println("用圆圆的肚子打豆豆");
}
}
// bigPenguin相同,省略...
public class test {
public static void main(String[] args) {
System.out.println("littlePenguin:");
littlePenguin penguin_1 = new littlePenguin();
penguin_1.eating();
penguin_1.sleeping();
penguin_1.beating();
// 下同,省略...
}
}
工作过一段时间的同学,可能会采用这种实现方式,我们有没有更优雅的实现方式呢?
模板模式举例
定义 :一个抽象类公开定义了执行它的方法的方式/模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,属于行为型模式。
这 3 只企鹅,因为 “吃饭,睡觉” 都一样,所以我们可以直接实现出来,但是他们 “打豆豆” 的方式不同,所以封装成抽象方法,需要每个企鹅单独去实现 “打豆豆” 的方式。
最后再新增一个方法 everyDay(),固定每天的执行流程:
public abstract class penguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public abstract void beating();
public void everyDay() {
this.eating();
this.sleeping();
this.beating();
}
}
每只企鹅单独实现自己 “打豆豆” 的方式:
public class littlePenguin extends penguin {
@Override
public void beating() {
System.out.println("用小翅膀打豆豆");
}
}
public class middlePenguin extends penguin {
@Override
public void beating() {
System.out.println("用圆圆的肚子打豆豆");
}
}
public class bigPenguin extends penguin {
@Override
public void beating() {
System.out.println("拿鸡毛掸子打豆豆");
}
}
最后看调用方式:
public class test {
public static void main(String[] args) {
System.out.println("littlePenguin:");
littlePenguin penguin1 = new littlePenguin();
penguin1.everyDay();
System.out.println("middlePenguin:");
middlePenguin penguin2 = new middlePenguin();
penguin2.everyDay();
System.out.println("bigPenguin:");
bigPenguin penguin3 = new bigPenguin();
penguin3.everyDay();
}
}
上面使用了设计模式中的模板模式作为例子,大家可以直面的感受出设计模式带来的优越感了吧!
下面开始正式的介绍设计模式。
3.设计模式遵循的六大原则
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
4.常用设计模式
4.1 单例模式
单例模式是java中老生常谈的设计模式,在工作中相信大家也没少接触,就小编个人而言,单例模式的主要应用场景如下:
适用于项目中频繁获取对象的场景,例如:获取缓存对象、获取一些工具类对象等等,由于这些对象使用频率较高,所以在获取对象时,我们使用单例模式指定获取一个对象即可。
下面小编带大家再次温习一下单例模式的写法,这里将介绍单例模式的五种写法,
饿汉模式 代码结构如下:
- 私有的静态的 最终的 对象 直接new
- 私有的 无参构造方法
- 共有的 静态的 实例方法
饿汉模式是单例模式中常用的写法之一,主要的特点是在定义对象的时候就直接new一个对象,详细代码如下:
public class SignletonHungry {
//1. 私有的静态的最终的对象
private static final SignletonHungry singl=new SignletonHungry();
//2. 私有的无参构造函数
private SignletonHungry(){
}
//3. 公共的静态的实例方法
public static SignletonHungry getInstance(){
return singl;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonHungry.getInstance().hashCode());
}).start();
}
}
}
执行结果如下:
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
可以看到执行结果是一致的,这种饿汉写法一般在实际项目中应用的也是最多的,
优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。
缺点:但是因为在指定对象时就进行初始化,在类比较大的时候,也会造成一定的资源消耗。
4.1.1 懒汉模式
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法
为了避免上述所说的饿汉式的缺点,延伸出了懒汉式的单例写法,在定义对象时,并不直接进行初始化,在实际的实例化方法里面,才进行初始化操作,这样就节省了一定的资源,具体实现代码如下:
public class SignletonFull {
//1. 私有的静态的对象 先不new 默认为null值
private static SignletonFull signletonFull;
//2. 私有的无参构造器
private SignletonFull(){}
//3. 公共的静态的方法
public static SignletonFull getInstance() throws InterruptedException {
if(signletonFull==null){
Thread.sleep(1000);
signletonFull=new SignletonFull();
}
return signletonFull;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
try {
System.out.println("获取的hashCode是: "+SignletonFull.getInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
执行结果如下:
获取的hashCode是:2131897467
获取的hashCode是:546217754
获取的hashCode是:592194369
获取的hashCode是:715203113
获取的hashCode是:567295441
获取的hashCode是:1336279646
获取的hashCode是:6907257361
获取的hashCode是:275611154
获取的hashCode是:1754774442
获取的hashCode是:1691297082
4.1.2 线程锁
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法,在判断对象是否为空时加上synchronize修饰
? 通过线程锁的写法可以解决懒汉模式下存在的线程安全问题,具体实现代码如下:
public class SignletonThread {
//1. 私有的静态的对象
private static SignletonThread signletonThread;
//2. 私有的构造方法
private SignletonThread(){}
//3. 公共的静态的实例方法 在if里面加上锁synchronized
public static SignletonThread getInstance(){
if (signletonThread==null){
synchronized (SignletonThread.class){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
signletonThread=new SignletonThread();
}
}
return signletonThread;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonThread.getInstance().hashCode());
}).start();
}
}
}
执行结果如下:
获取的hashCode是:508349366
获取的hashCode是:1984363717
获取的hashCode是:1383719481
获取的hashCode是:609137758
获取的hashCode是:1444142116
获取的hashCode是:2127199882
获取的hashCode是:485324444
获取的hashCode是:1062809210
获取的hashCode是:1858348566
获取的hashCode是:1889411282
获取的hashCode是:642317370
4.1.3 双重判断模式
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法,在判断对象是否为空时加上synchronize修饰
- 在判断里面再次判断是否为空
public class SignletonThreadTwo {
//1. 私有的静态的对象
private static SignletonThreadTwo signletonThreadTwo;
//2. 私有的构造方法
private SignletonThreadTwo(){}
//3. 公共的静态的实例方法 在if里面加上锁synchronized 在锁块中继续判断是否为空
public static SignletonThreadTwo getInstance(){
if (signletonThreadTwo==null){
synchronized (SignletonThreadTwo.class){
if(signletonThreadTwo==null){
signletonThreadTwo=new SignletonThreadTwo();
}
}
}
return signletonThreadTwo;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonThreadTwo.getInstance().hashCode());
}).start();
}
}
}
执行结果如下:
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashCode是:995212737
获取的hashcode是:995212737
获取的hashCode是:995212737
4.2 策略模式
代码结构如下:
- 定义策略接口,定义通用方法。
- 定义N个实现类,实现接口,重新方法。
- 定义Context上下文类,利用多态进行封装。
- 使用时通过Context上下文类进行调用,在构造函数中传入实现类的对象。
策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略,可以使得算法可独立于使用它的用户而变化。
使用场景:
? 1.假设现在某超市有三个等级的会员,普通会员,VIP1,VIP2。
2. 在结账的时候,三个登记的会员购买了同一种商品,普通会员不打折,VIP1打9折,VIP2打8折
定义策略接口:
public interface StrategyInt {
//price价格 n数量
public double getPrice(double price,int n);
}
定义普通会员,实现策略接口:
public class NormalPerson implements StrategyInt {
//普通会员不打折
@Override
public double getPrice(double price, int n)
{
System.out.println("普通会员不打折.....");
return (price*n);
}
}
定义vip1会员,实现策略接口:
public class Vip1Person implements StrategyInt{
//VIP1客户 打9折
@Override
public double getPrice(double price, int n) {
System.out.println("VIP1打9折.....");
return (price*n)*0.9;
}
}
定义vip2会员,实现策略接口:
public class Vip2Person implements StrategyInt {
@Override
public double getPrice(double price, int n) {
System.out.println("VIP2打8折.....");
return (price*n)*0.8;
}
}
定义context上下文类,利用多态进行封装:
public class PersonContext {
//1. 定义私有对象
private StrategyInt strategyInt;
//2. 定义有参构造方法
public PersonContext(StrategyInt strategyInt) {
this.strategyInt = strategyInt;
}
//3. 定义计算价格的方法
public double getPrice(double price,int n){
return strategyInt.getPrice( price, n);
}
}
定义测试类,调用方法:
public class StrategyTest {
public static void main(String[] args) {
//定义三个类型的对象
NormalPerson normalPerson = new NormalPerson();
Vip1Person vip1Person = new Vip1Person();
Vip2Person vip2Person = new Vip2Person();
//new context类对象 将三个类型的对象传入
PersonContext npersonContext = new PersonContext(normalPerson);
PersonContext v1personContext = new PersonContext(vip1Person);
PersonContext v2personContext = new PersonContext(vip2Person);
//利用多态 通过调用context类对象的计算价格方法 实际上调用的子类的计算价格方法 得到最终价格
System.out.println("普通会员: "+npersonContext.getPrice(300,20));
System.out.println("VIP1: "+v1personContext.getPrice(300,20));
System.out.println("VIP2: "+v2personContext.getPrice(300,20));
}
}
执行结果如下:
普通会员不打折.....普通会员: 6009.0
VIP1打9折.....
VIP1: 5400.0
VIP2打8折.....
VIP2: 4899.9
4.3 工厂模式
? 相信大家也没少使用工厂模式,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 几次对象就不用了, 这种情况,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
4.3.1 简单工厂
? 简单工厂是工厂模式的简单实现的写法,这种写法比较简便,同时也带来了耦合的问题,我们来定义飞机和汽车以及生产他们的工厂类,主要实现代码如下:
飞机类:
public class Plane {
public void go(){
System.out.println("飞机嗖嗖嗖....");
}
}
汽车类:
public class Car {
public void go(){
System.out.println("汽车滴滴滴....");
}
}
生产交通工具的工厂类 :
public class ProductFactory {
public Car getCar(){
return new Car();
}
public Plane getPlane(){
return new Plane();
}
}
测试类:
public class TestClass {
public static void main(String[] args) {
Car car = new ProductFactory().getCar();
Plane plane = new ProductFactory().getPlane();
car.go();
plane.go();
}
}
执行结果如下:
汽车滴滴滴....
飞机嗖嗖嗖....
4.3.2抽象工厂
应?场景:
- 解耦:分离职责,把复杂对象的创建和使?的过程分开。
- 复用代码降低维护成本:如果对象创建复杂且多处需?到,如果每处都进?编写,则很多重复代码,如果业务逻辑发?了改 变,需?四处修改;使???模式统?创建,则只要修改??类即可, 降低成本。
代码结构如下:以新能源汽车为例,五菱和特斯拉。
- 定义汽车接口,汽车接口中定义了一些方法,比如启动、运行、关闭。
- 定义特斯拉和五菱的汽车类,重写汽车接口的这些方法。
- 定义生产汽车的工厂接口,接口中有生产汽车的方法,返回值类型为汽车接口。
- 分别定义特斯拉和五菱的工厂类,实现生产汽车的工厂接口,重写生产汽车的方法。
- 测试类中直接new五菱和特斯拉的工厂类,生产出相关的产品,调用启动、运行、关闭方法。
定义汽车接口:
public interface CarProduct {
//启动
void start();
//跑
void run();
//关闭
void shutDown();
}
定义特斯拉汽车类,实现汽车接口:
public class TeslaCar implements CarProduct {
@Override
public void start() {
System.out.println("特斯拉启动了");
}
@Override
public void run() {
System.out.println("特斯拉跑了");
}
@Override
public void shutDown() {
System.out.println("特斯拉关闭了");
}
}
定义五菱汽车类,实现汽车接口:
public class WulingCar implements CarProduct {
@Override
public void start() {
System.out.println("五菱启动了");
}
@Override
public void run() {
System.out.println("五菱开始跑了");
}
@Override
public void shutDown() {
System.out.println("五菱关闭了");
}
}
定义生产汽车的工厂类:
public interface CarproductFactory {
CarProduct productCar();
}
定义生产五菱汽车的工厂类:
public class WuLingFactory implements CarproductFactory {
@Override
public CarProduct productCar() {
return new WulingCar();
}
}
定义生产特斯拉的工厂类:
public class TeslaFactory implements CarproductFactory {
@Override
public CarProduct productCar() {
return new TeslaCar();
}
}
测试类:
public class TestFactory {
public static void main(String[] args) {
//生产五菱汽车
WuLingFactory wuLingFactory = new WuLingFactory();
CarProduct carProduct = wuLingFactory.productCar();
carProduct.start();
carProduct.run();
carProduct.shutDown();
System.out.println("*******************************");
//生产特斯拉汽车
TeslaFactory teslaFactory = new TeslaFactory();
CarProduct carProduct1 = teslaFactory.productCar();
carProduct1.start();
carProduct1.run();
carProduct1.shutDown();
}
}
执行结果如下:
五菱启动了
五菱开始跑了
五菱关闭了
*******************************
特斯拉启动了
特斯拉跑了
特斯拉关闭了
4.4 装饰器模式
装饰器模式是一种 对象结构型模式 ,它通过一种无须定义子类的方式来给对象动态增加职责/功能,使用对象之间的关联关系取代类之间的继承关系。
应用场景如下:
举例说明:
例如五菱汽车,五菱汽车首先能跑,这种特性是属于不变的特性。
而其他的型号则是不同的,例如续航不同、是否敞篷、是否有智能语音等等。
针对不变的特性可以定义为接口,针对变化的特性可以定义装饰器,装饰器就是在原有的基础上增加了一些修饰。
代码结构如下:
- 定义五菱汽车抽象类,通用方法
- 定义敞篷版、gameboy版本继承至五菱汽车抽象类,重写通用方法。
- 定义五菱的装饰器类,继承至五菱汽车抽象类,传入五菱汽车抽象类型的对象,调用五菱汽车抽象类型的通用方法。
- 分别定义五菱汽车的敞篷装饰器和续航增强装饰器,继承至五菱汽车的装饰器类,重写通用方法,增加装饰器功能。
- 通过测试类调用,new 五菱敞篷对象,传入敞篷装饰器,调用装饰器的run方法。
定义五菱新能源汽车的抽象类,定义run方法:
public abstract class WulingNewEngeryCar {
abstract void run();
}
定义五菱新能源的具体型号,例如gameboy,继承至五菱新能源类:
public class WulingGameBoy extends WulingNewEngeryCar {
@Override
void run() {
System.out.println("五菱gameBoy");
}
}
定义五菱新能源的具体型号,例如敞篷版,继承至五菱新能源类:
public class Wulingchangpeng extends WulingNewEngeryCar{
@Override
void run() {
System.out.println("敞篷版五菱");
}
}
定义五菱新能源汽车的装饰类,装饰类也继承自新能源类:
public abstract class WulingDecorate extends WulingNewEngeryCar{
//私有的对象
private WulingNewEngeryCar wulingNewEngeryCar;
//公共的构造函数
public WulingDecorate(WulingNewEngeryCar wulingNewEngeryCar) {
this.wulingNewEngeryCar = wulingNewEngeryCar;
}
//重写汽车的能力
@Override
void run() {
wulingNewEngeryCar.run();
}
}
定义五菱敞篷版的装饰类 ,继承自五菱汽车的装饰类:
public class NoDoorDecorate extends WulingDecorate{
//调用父类的构造方法
public NoDoorDecorate(WulingNewEngeryCar wulingNewEngeryCar) {
super(wulingNewEngeryCar);
}
//增加装饰
@Override
void run() {
super.run();
System.out.println("增加敞篷功能");
}
}
定义五菱续航版本的装饰类 ,继承自五菱汽车的装饰类:
public class RunLongDecorate extends WulingDecorate{
public RunLongDecorate(WulingNewEngeryCar wulingNewEngeryCar) {
super(wulingNewEngeryCar);
}
@Override
void run() {
super.run();
System.out.println("续航增强");
}
}
测试类:
public class AppTestClass {
public static void main(String[] args) {
//new 一个wuling敞篷类
Wulingchangpeng wulingchangpeng=new Wulingchangpeng();
//调用敞篷装饰器 增加敞篷功能
NoDoorDecorate noDoorDecorate = new NoDoorDecorate(wulingchangpeng);
noDoorDecorate.run();
RunLongDecorate runLongDecorate1 = new RunLongDecorate(wulingchangpeng);
runLongDecorate1.run();
System.out.println("*****************");
WulingGameBoy wulingGameBoy = new WulingGameBoy();
RunLongDecorate runLongDecorate = new RunLongDecorate(wulingGameBoy);
runLongDecorate.run();
}
}
执行结果如下:
敞篷版五菱
增加敞篷功能
敞篷版五菱
续航增强
*****************
五菱gameBoy
续航增强
4.5 观察者模式
观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系(注册),使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新(通知)。说白了就是个注册,通知的过程。
代码结构如下:
- 定义观察者接口和观察者方法。
- 定义接口的实现类A和B
- 定义主题类,将观察者传入一个集合内
- 调用通知方法,循环观察者,传递参数给观察者做出响应。
下面使用代码模拟股市在变动时, 游资和基金之间的动向,这种场景能形象生动的展示出观察者模式:
定义股市观察者接口:
public interface SharsObserver {
//观察之后做出如何反映
public void response(int i);
}
定义游资类,实现观察者接口:
public class PersonObserver implements SharsObserver{
@Override
public void response(int i) {
if(i>0){
System.out.println("游资: 涨了,快点投资投资.......");
}else{
System.out.println("游资: 跌了,快点撤资撤资.......");
}
}
}
定义基金类,实现观察者接口:
public class CompanyObserver implements SharsObserver {
@Override
public void response(int i) {
if(i>0){
System.out.println("机构: 涨了,再拉点投资吧.......");
}else{
System.out.println("机构: 跌了,稳一点,先不动.......");
}
}
}
定义主题类,将观察者的动态组装在一起:
public class Subject {
//储存游资和基金的对象
List list=new ArrayList();
//新增观察者
public void addObserver(SharsObserver sharsObserver){
list.add(sharsObserver);
}
//通知观察者
public void change(int j){
for (int i = 0; i < list.size(); i++) {
SharsObserver sharsObserver = list.get(i);
sharsObserver.response(j);
}
}
}
测试类:
public class TestMain {
public static void main(String[] args) {
CompanyObserver companyObserver = new CompanyObserver();
PersonObserver personObserver = new PersonObserver();
Subject subject = new Subject();
subject.addObserver(companyObserver);
subject.addObserver(personObserver);
subject.change(2);
System.out.println("---------------------");
subject.change(0);
}
}
执行结果如下:
机构: 涨了,再拉点投资吧.......
游资: 跌了,快点撤资撤资.......
---------------------
机构: 跌了,稳一点,先不动.......
游资: 跌了,快点撤资撤资.......
4.6 享元模式
*享元模式:* 通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率,将重复出现的内容作为共享部分取出,由多个对象共享一份,从而减轻内存的压力
应用场景:
? String的实现就是享元模式,底层有针对各种字符的常量池,有变量引用到常量时,就直接引用常量池中的常量,又例如数据库连接池,也是利用享元模式。
下面请看模拟数据库连接的代码实现:
定义数据库连接资源基类 其他类需要继承此类:
public abstract class DataSource {
String dataId;
String dataName;
public String getDataId() {
return dataId;
}
public String getDataName() {
return dataName;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
public void setDataName(String dataName) {
this.dataName = dataName;
}
public DataSource(String dataId, String dataName) {
this.dataId = dataId;
this.dataName = dataName;
}
public abstract void method();
@Override
public String toString() {
return "DataSource{" +
"dataId='" + dataId + '\'' +
", dataName='" + dataName + '\'' +
'}';
}
}
定义数据库对象生产者类:
public class DataSourceMaker extends DataSource{
public DataSourceMaker(String dataId, String dataName) {
super(dataId, dataName);
}
@Override
public void method() {
System.out.println("使用DataSourceMaker1生产数据库连接对象.....");
}
}
定义工厂类,生产数据库连接对象:
public class DataSourceFactory {
private static HashMap hashMap=new HashMap<>();
public DataSourceFactory() {
for (int i = 0; i < 10; i++) {
DataSourceMaker dataSourceMaker = new DataSourceMaker(String.valueOf(i), "DataSource" + i);
hashMap.put(dataSourceMaker.getDataId(),dataSourceMaker);
}
}
public DataSource getDataSourceFactory(String datasourceName){
if (hashMap.containsKey(datasourceName)){
return hashMap.get(datasourceName);
}
return null;
}
}
定义测试类:
public class TestDataSource {
public static void main(String[] args) {
//取出数据库连接对象
DataSourceFactory dataSourceFactory = new DataSourceFactory();
for (int i = 0; i < 10; i++) {
System.out.println("i: "+dataSourceFactory.getDataSourceFactory(String.valueOf(i)).toString());
}
}
}
执行结果如下:
i: DataSource{dataId='2',dataName='DataSource2'}
i: DataSource{dataId='3',dataName='DataSource3'}
i: DataSource{datald='4',dataName='DataSource4'}
i: DataSource{dataId='5',dataName='DataSource5'}
i: DataSource{dataId='6',dataName='DataSource6'}
i: DataSource{dataId='7',dataName='DataSource7'}
i: DataSource{dataId='8',dataName='DataSource8'}
i: DataSource{dataId='9',dataName='DataSource9'}
4.7 门面模式
? 门面模式(Facade Pattern):也叫外观模式,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。 门面模式提供一个高层次的接口,使得子系统更易于使用。
适用场景:
? 多个子系统类相互之间存在强依赖的关系,通过门面类统一处理复杂关系的调用,外部类调用时,可以调用门面类,无需再调用子系统类。
门面模式相对来说比较简单,代码实现如下:
定义系统A类,通过A类实现与系统A的通信:
public class SystemA {
public String doA(){
return "A";
}
}
定义系统B类,通过B类实现与系统A的通信:
public class SystemB {
public String doB(){
return "B";
}
}
定义系统C类,通过C类实现与系统A的通信:
public class SystemC {
public String doC(){
return "C";
}
}
定义门面类,通过门面类实现A、B、C三个系统之间的复杂调用:
public class ControlClas {
//私有化三个系统的类
private SystemA systemA=new SystemA();
private SystemB systemB=new SystemB();
private SystemC systemC=new SystemC();
//通过此方法实现A、B、C之间的复杂调用
public void doSomthing(){
//自定义逻辑
systemA.doA();
//自定义逻辑
systemB.doB();
//自定义逻辑
systemC.doC();
}
}
? 我们可以看到,通过最后的control类,我们可以在其中控制系统A、B、C之间相互调用的复杂场景,而在外部的类去调用A、B、C之间实现业务的时候,可以直接调用门面类去实现,无需在A、B、C中定义复杂代码。
4.8 代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
4.8.1 静态代理模式
? 静态代理就是通过自定义的类,去实现代理过程的一种模式,他只能代理这个类,要想代理其他类,要想代理其他类需要写新的代理方法。
代码结构如下:
- 定义汽车接口
- 定义汽车接口的实现类,实现汽车接口。
- 定义汽车的代理类,实现汽车接口。
- 在代理类中注入接口类型的对象,调用run方法,在run方法前后实现新方法的调用
定义汽车接口:
public interface CarInterface {
//汽车可以跑
public void run();
}
定义汽车类,实现汽车接口:
public class Car implements CarInterface {
@Override
public void run() {
System.out.println("汽车在跑.......");
}
}
定义汽车的代理类,也实现汽车接口:
public class Carproxy implements CarInterface {
//私有化汽车类
private Car car;
//创建构造函数
public Carproxy(Car car) {
this.car = car;
}
//调用汽车类的run方法 在run之前和之后可以定义新方法
@Override
public void run() {
beforeRun();
car.run();
afterRun();
}
//汽车运行之前调用的方法
private void afterRun() {
System.out.println("汽车打开火了.....");
}
//汽车运行之后调用的方法
private void beforeRun() {
System.out.println("汽车熄火了.....");
}
}
测试类:
public class TestMain {
public static void main(String[] args) {
Carproxy carproxy = new Carproxy(new Car());
carproxy.run();
}
}
执行结果:
汽车熄火了.....
汽车在跑.......
汽车打开火了.....
? 通过静态代理,我们可以简单的实现对某个类、接口的代理,但是静态代理也有一定的局限性, 如果我们需要对某个新类进行代理时,又需要代理类实现新的接口去重写一些方法,这样显然是不太方便的,所以JDK给我们提供了动态代理的方法。
4.8.2 动态代理模式
? 动态代理就是JDK帮我们实现的,其原理是基于类的反射,动态的为我们生成一个代理类,帮我们执行方法,在执行方法的前后位置我们可以定义一些自定义的方法,下面我们来看代码结构:
- 定义汽车接口
- 定义汽车接口的实现类,实现汽车接口。
- 定义代理类,注入接口类型的对象,调用Proxy的newProxyInstence方法,传入对象.getclass.getclassloader,传入对象.getClass.getInterfaces,传入内部类InvocationHandler,在内部类中实现调用。
定义汽车接口:
public interface CarInterface {
public void run();
}
定义汽车类,实现接口:
public class Car implements CarInterface{
@Override
public void run() {
System.out.println("汽车跑起来了......");
}
}
定义汽车类的代理类(也可以直接在测试类中实现,作用主要就是封装代理过程):
public class CarMoveProxy {
//利用JDK的动态代理实现代理模式
public void doRun(Car car,InvocationHandler invocationHandler){
//第一个参数 对象.getClass.getClassLoder 第二个参数 对象.getCalss.getInterfaces 第三个参数 invocationHandler内部类
CarInterface car1= (CarInterface) Proxy.newProxyInstance(car.getClass().getClassLoader(),
car.getClass().getInterfaces(),
invocationHandler);
car1.run();
}
}
测试类:
public class TestProxy {
public static void main(String[] args) {
CarMoveProxy carMoveProxy = new CarMoveProxy();
Car car = new Car();
carMoveProxy.doRun(car, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("汽车启动...");
Object invoke = method.invoke(car, objects);
System.out.println("汽车关闭...");
return invoke;
}
});
}
}
执行结果如下:
汽车启动...
汽车跑起来了.....
汽车关闭...
5.结束语
看完这篇文章,相信这 3几种设计模式,已经深深刻在你骨子里面了。
? 大家可以静下心来想想,自己之前做过的项目中,有哪些用到上面这其中几种设计模式,然后自己再结合具体的场景总结一下,我想你应该会有更深入的理解。
本文暂时没有评论,来添加一个吧(●'◡'●)