转载:https://www.cnblogs.com/stonefeng/p/5679638.html
https://blog.csdn.net/zdy0_2004/article/details/50703496
https://www.cnblogs.com/lixiuyu/p/5923160.html
https://www.cnblogs.com/lixiuyu/p/5919643.html
外观模式 转载【作者:刘伟 http://blog.csdn.net/lovelion】
装饰者模式
公司门口有一个小摊卖手抓饼和肉夹馍的,有时候中午不想吃饭就会去光顾一下那个小摊,点了手抓饼之后往往还可以在这个基础之上增加一些配料,例如煎蛋,火腿片等等,每个配料的价格都不一样,不管你怎么配配料,最终价格是手抓饼基础价加上每一种所选配料价格的总和。小摊的价格单如下:
如何使用一种设计模式来处理价格计算的问题呢,或许我们可以试试装饰者模式,因为在这里,主体是手抓饼和肉夹馍,而配料则是装饰者,我先用UML类图来描述一下类之间的协作关系。
再来看看具体java代码是怎么实现的:
1 public abstract class Pancake {
2
3 public String desc = "我不是一个具体的煎饼";
4
5 public String getDesc() {
6 return desc;
7 }
8
9 public abstract double price();
10
11
public class TornCake extends Pancake {
public TornCake() {
desc = "手抓饼";
}
@Override
public double price() {
return 4;
}
}
public class Roujiamo extends Pancake {
public Roujiamo() {
desc = "肉夹馍";
}
@Override
public double price() {
return 6;
}
}
public abstract class Condiment extends Pancake {
public abstract String getDesc();
}
public class FiredEgg extends Condiment {
private Pancake pancake;
public FiredEgg(Pancake pancake) {
this.pancake = pancake;
}
@Override
public String getDesc() {
return pancake.getDesc() + ", 煎蛋";
}
@Override
public double price() {
return pancake.price() + 2;
}
}
public class Ham extends Condiment {
private Pancake pancake;
public Ham(Pancake pancake) {
this.pancake = pancake;
}
@Override
public String getDesc() {
return pancake.getDesc() + ", 火腿片";
}
@Override
public double price() {
return pancake.price() + 1.5;
}
}
MeatFloss类和Cucumber类跟FiredEgg很相似,我就不一一列出来了,最后看看测试类:
public class MyTest {
@Test
public void test() {
Pancake tornCake = new TornCake();
//手抓饼基础价
System.out.println(String.format("%s ¥%s", tornCake.getDesc(), tornCake.price()));
Pancake roujiamo = new Roujiamo();
roujiamo = new FiredEgg(roujiamo);
roujiamo = new FiredEgg(roujiamo);
roujiamo = new Ham(roujiamo);
roujiamo = new MeatFloss(roujiamo);
roujiamo = new Cucumber(roujiamo);
//我好饿
System.out.println(String.format("%s ¥%s", roujiamo.getDesc(), roujiamo.price()));
}
}
在Java源码中典型的装饰者模式就是java I/O, 其实装饰者模式也有其缺点,就是产生了太多的装饰者小类,有类爆炸的趋势
http://www.cnblogs.com/intsmaze/p/5202213.html
装饰模式(Decorator)
装饰模式又名包装(Wrapper)模式。
装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。
装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。
装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。
装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。
装饰模式的角色
抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。
具体构件角色(Concrete Component):定义将要接收附加责任的类。
装饰角色(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口。
具体装饰角色(Concrete Decorator):负责给构件对象“贴上”附加的责任。
Java IO中的装饰模式
在IO中,具体构件角色是节点流,装饰角色是过滤流。
FilterInputStream和FilterOutputStream是装饰角色,而其他派生自它们的类则是具体装饰角色。
装饰模式的特点
装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
装饰对象包含一个真实对象的引用(reference)。
装饰对象接收所有来自客户端的请求,它把这些请求转发给真实的对象。
装饰对象可以在转发这些请求之前或之后附加一些功能。
这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
程序实例
public interface Component{
public void doSomething();
}
这是抽象构件角色,是一个接口。具体构件角色实现这个接口:
public class ConcreteComponent implements Component{
@Override
public void doSomething() {
System.out.println("功能A");
}
}
装饰角色:
public class Decorator implements Component{
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void doSomething() {
component.doSomething();
}
}
其中包含了构件角色的引用,方法调用中利用构件角色的方法。
具体装饰角色(两个):
public class ConcreteDecorator1 extends Decorator{
public ConcreteDecorator1(Component component) {
super(component);
}
@Override
public void doSomething() {
super.doSomething();
this.doAnotherThing();
}
private void doAnotherThing() {
System.out.println("功能B");
}
}
public class ConcreteDecorator2 extends Decorator{
public ConcreteDecorator2(Component component) {
super(component);
}
@Override
public void doSomething() {
super.doSomething();
this.doAnotherThing();
}
private void doAnotherThing() {
System.out.println("功能C");
}
}
使用测试:
public class Client{
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component component1 = new ConcreteDecorator1(component);
component1.doSomething();
System.out.println("-----------");
Component component2 = new ConcreteDecorator2(component1);
component2.doSomething();
}
}
问题引入
咖啡店的类设计:
一个饮料基类,各种饮料类继承这个基类,并且计算各自的价钱。
饮料中需要加入各种调料,考虑在基类中加入一些布尔值变量代表是否加入各种调料,基类的cost()中的计算各种调料的价钱,子类覆盖cost(),并且在其中调用超类的cost(),加上特定饮料的价钱,计算出子类特定饮料的价钱。
缺点:类数量爆炸、基类加入的新功能并不适用于所有的子类、调料价钱的改变、新调料的出现都会要求改变现有代码;有的子类并不适合某些调料等情况……
设计原则
类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
要让OO设计同时具备开放性和关闭性,不是一件容易的事,通常来说,没有必要把设计的每个部分都这么设计。
遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。
我们需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。
用装饰者模式解决问题
解决咖啡店饮料问题的方法:
以饮料为主体,然后在运行时以调料来“装饰”饮料。
比如,顾客想要摩卡(Mocha)和奶泡(Whip)深焙咖啡(DarkRoast):
DarkRoast继承自Beverage,有一个cost()方法。
第一步,以DarkRoast对象开始;
第二步,顾客想要摩卡,所以建立一个Mocha装饰者对象,并用它将DarkRoast对象包装(wrap)起来;
第三步,顾客想要奶泡,所以建立一个Whip装饰者对象,并用它将Mocha对象包起来;(Mocha和Whip也继承自Beverage,有一个cost()方法);
最后,为顾客算钱,通过调用最外圈装饰者(Whip)的cost()就可以。Whip()的cost()会先委托它装饰的对象(Mocha)计算出价钱,然后在加上奶泡的价钱。Mocha的cost()也是类似。
装饰者模式的特点
装饰者和被装饰对象有相同的超类型。
可以用一个或多个装饰者包装一个对象。
因为装饰者和被装饰者具有相同的类型,所以任何需要原始对象的场合,可以用装饰过的对象代替。
装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
装饰者模式的定义
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式的实现
实现类图如下:
装饰者和被装饰者具有共同的超类,利用继承达到“类型匹配”,而不是利用继承获得“行为”;将装饰者和被装饰者组合时,加入新的行为。
解决本文中饮料的具体问题时,图中Component即为Beverage(可以是抽象类或者接口),而ConcreteComponent为各种饮 料,Decorator(抽象装饰者)为调料的抽象类或接口,ConcreteDecoratorX则为各种具体的调料。
因为使用对象组合,可以把饮料和调料更有弹性地加以混合与匹配。
代码外部细节:
代码中实现的时候,通过构造函数将被装饰者传入装饰者中即可,如最后的调用形式如下:
Beverage beverage = new DarkRoast();
beverage = new Mocha(beverage);
beverage = new Whip(beverage);
即完成了两层包装,此时再调用beverage的cost()函数即可得到总价。
java.io包内的装饰者模式
装饰者模式的缺点:在设计中加入大量的小类,如果过度使用,会让程序变得复杂。
桥接模式
Bridge(桥接)
桥接模式(Bridge)是一种结构型设计模式。Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
桥接模式的角色和职责:
1.Client 调用端
这是Bridge模式的调用者。
2.抽象类(Abstraction)
抽象类接口(接口这货抽象类)维护队行为实现(implementation)的引用。它的角色就是桥接类。
3.Refined Abstraction
这是Abstraction的子类。
4.Implementor
行为实现类接口(Abstraction接口定义了基于Implementor接口的更高层次的操作)
5.ConcreteImplementor
Implementor的子类
意图:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
适用性:
你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
(C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh 称这种类层次结构为“嵌套的普化”(nested generalizations )。
你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是Coplien 的String 类[ Cop92 ],在这个类中多个对象可以共享同一个字符串表示(StringRep )。
生活中的一个例子:
就拿汽车在路上行驶的来说。即有小汽车又有公共汽车,它们都不但能在市区中的公路上行驶,也能在高速公路上行驶。这你会发现,对于交通工具(汽车)有不同的类型,然而它们所行驶的环境(路)也在变化,在软件系统中就要适应两个方面的变化?怎样实现才能应对这种变化呢?
概述:
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。
意图:
将抽象部分与实现部分分离,使它们都可以独立的变化。
——《设计模式》GOF
结构图:
传统的做法:
通过类继承的方式来做上面的例子;
先看一下类结构图:
代码实现:
1namespace CarRunOnRoad
2{
3 //路的基类;
4 public class Road
5 {
6 public virtual void Run()
7 {
8 Console.WriteLine("在路上");
9 }
10 }
11 //高速公路;
12 public class SpeedWay : Road
13 {
14 public override void Run()
15 {
16 Console.WriteLine("高速公路");
17 }
18 }
19 //市区街道;
20 public class Street : Road
21 {
22 public override void Run()
23 {
24 Console.WriteLine("市区街道");
25 }
26 }
27 //小汽车在高速公路上行驶;
28 public class CarOnSpeedWay : SpeedWay
29 {
30 public override void Run()
31 {
32 Console.WriteLine("小汽车在高速公路上行驶");
33 }
34 }
35 //公共汽车在高速公路上行驶;
36 public class BusOnSpeedWay : SpeedWay
37 {
38 public override void Run()
39 {
40 Console.WriteLine("公共汽车在高速公路上行驶");
41 }
42 }
43 //小汽车在市区街道上行驶;
44 public class CarOnStreet : Street
45 {
46 public override void Run()
47 {
48 Console.WriteLine("汽车在街道上行驶");
49 }
50 }
51 //公共汽车在市区街道上行驶;
52 public class BusOnStreet : Street
53 {
54 public override void Run()
55 {
56 Console.WriteLine("公共汽车在街道上行驶");
57 }
58 }
59
60}
客户端调用:
1static void Main(string[] args)
2 {
3 //小汽车在高速公路上行驶
4 CarOnSpeedWay Car = new CarOnSpeedWay();
5 Car.Run();
6
7 Console.WriteLine("===========================");
8
9 //公共汽车在街道上行驶
10 BusOnStreet Bus = new BusOnStreet();
11 Bus.Run();
12
13 Console.Read();
14 }
缺点:
但是我们说这样的设计是脆弱的,仔细分析就可以发现,它还是存在很多问题,首先它在遵循开放-封闭原则的同时,违背了类的单一职责原则,即一个类只有一个引起它变化的原因,而这里引起变化的原因却有两个,即路类型的变化和汽车类型的变化;其次是重复代码会很多,不同的汽车在不同的路上行驶也会有一部分的代码是相同的;再次是类的结构过于复杂,继承关系太多,难于维护,最后最致命的一点是扩展性太差。如果变化沿着汽车的类型和不同的道路两个方向变化,我们会看到这个类的结构会迅速的变庞大。
应用设计模式
桥接模式(Bridge)来做;
先看一下类结构图:
代码实现:
1namespace CarRunOnRoad_Bridge_
2{
3
4 //抽象路
5 public abstract class AbstractRoad
6 {
7 protected AbstractCar car;
8 public AbstractCar Car
9 {
10 set
11 {
12 car = value;
13 }
14 }
15
16 public abstract void Run();
17 }
18
19 //高速公路
20 public class SpeedWay : AbstractRoad
21 {
22 public override void Run()
23 {
24 car.Run();
25 Console.WriteLine("高速公路上行驶");
26 }
27 }
28
29 //市区街道
30 public class Street : AbstractRoad
31 {
32 public override void Run()
33 {
34 car.Run();
35 Console.WriteLine("市区街道上行驶");
36 }
37 }
38}
1namespace CarRunOnRoad_Bridge_
2{
3 //抽象汽车
4 public abstract class AbstractCar
5 {
6 public abstract void Run();
7 }
8
9 //小汽车;
10 public class Car : AbstractCar
11 {
12 public override void Run()
13 {
14 Console.Write("小汽车在");
15 }
16 }
17
18 //公共汽车
19 public class Bus : AbstractCar
20 {
21 public override void Run()
22 {
23 Console.Write("公共汽车在");
24 }
25 }
26}
客户端调用:
1 static void Main(string[] args)
2 {
3 //小汽车在高速公路上行驶;
4 AbstractRoad Road1 = new SpeedWay();
5 Road1.Car = new Car();
6 Road1.Run();
7 Console.WriteLine("=========================");
8
9 //公共汽车在高速公路上行驶;
10 AbstractRoad Road2 = new SpeedWay();
11 Road2.Car = new Bus();
12 Road2.Run();
13
14
15
16 Console.Read();
17 }
可以看到,通过对象组合的方式,Bridge 模式把两个角色之间的继承关系改为了关联的关系,降低了耦合,从而使这两者可以从容自若的各自独立的变化,这也是Bridge模式的本意。
这样增加了客户程序与路与汽车的耦合。其实这样的担心是没有必要的,因为这种耦合性是由于对象的创建所带来的,完全可以用创建型模式去解决。在应用时结合创建型设计模式来处理具体的问题。
应用设计模式:
桥接模式(Bridge)来做(多维度变化);
结合上面的例子,增加一个维度"人",不同的人开着不同的汽车在不同的路上行驶(三个维度);
结合上面增加一个类"人",并重新调用.
代码实现:
1namespace CarRunOnRoad_Bridge_
2{
3 abstract class people
4 {
5 AbstractRoad road;
6 public AbstractRoad Road
7 {
8 get
9 {
10 return road;
11 }
12 set
13 {
14 road = value;
15 }
16 }
17 public abstract void Run();
18
19 }
20 class Man : people
21 {
22 public override void Run()
23 {
24 Console.Write("男人开着");
25 Road.Run();
26 }
27 }
28
29 class WoMan : people
30 {
31 public override void Run()
32 {
33 Console.Write("女人开着");
34 Road.Run();
35 }
36 }
37}
客户端调用:
1 static void Main(string[] args)
2 {
3
4 //男人开着公共汽车在高速公路上行驶;
5 Console.WriteLine("=========================");
6
7 AbstractRoad Road3 = new SpeedWay();
8 Road3.Car = new Bus();
9
10 people p = new Man();
11 p.Road = Road3;
12 p.Run();
13
14 Console.Read();
15 }
效果及实现要点:
1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
2.所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同路上的不同汽车。
3.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
4.Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。
适用性:
在以下的情况下应当使用桥梁模式:
1.如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
2.设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
3.一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
4.虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
总结:
Bridge模式是一个非常有用的模式,也非常复杂,它很好的符合了开放-封闭原则和优先使用对象,而不是继承这两个面向对象原则。
桥接模式与装饰的区别:
装饰模式:
这两个模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合可以实现很多的功能组合 .
桥接模式:
桥接模式则把原来的基类的实现化细节抽象出来,在构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度上的独立变化 。
桥接模式最典型的应用时JDBC
桥接模式是由Gang of four整理的23种设计模式中的一种,JDBC是桥接模式一个典型的例子。了解JDBC源码,能让我们更好地理解桥接模式的意图和实现;了解桥接模式,能让我们更清楚JDBC设计的优越之处。
首先我们先来看下桥接模式的意图,它旨在将抽象部分与实现部分分离,使其可以独立地变化,废话不多说,我们直接看一张UML图:
其实最初我在看这句意图的时候非常的郁闷,作为一名开发人员,对抽象与实现的第一反应就是接口实现或者类继承,接口和实现类独立地变化本身就是一件让我觉得很郁闷的事情,但是看了这张UML图之后便恍然大悟了,这里的抽象与实现是聚合关系,也就是我们口头常说的调用者和被调用者,这样一来桥接模式的意图就很清楚了,其实某种意义上就是解耦了某个功能的抽象定义和具体实现,让其变成了两个互相独立的模块。
让我们回到JDBC的源码,首先请大家打开JDK API,翻到java.sql包:
可以发现,java.sql包不同于其他几乎所有的包,它基本可以说没有定义多少类,但是定义了大量的接口,而由各个数据库公司去写这些接口的实现类:
可以看到,sum公司仅仅是提出了一系列接口规范,数据库公司做出实现,然后当你真的需要连接某个数据库时,你需要先添加数据库公司发布的jar包,如jdbc-mysql.jar,然后在使用时先加载Driver:
然后就可以通过DriverManager去获取连接:
这里的conn的定义依旧是以java.sql中定义的接口来声明的,也就是说,我们在代码中依旧使用的是接口,只不过通过了最初的加载Driver让添加的jar包中的类成为了真正的实现,但是这个加载的过程究竟是怎么实现的呢???
这里需要牵扯到一个别的知识,Class.forName和ClassLoader类都能用于加载java类获得Class对象,那这二者究竟有什么区别呢?
答案是,ClassLoader只能将类填放进入JVM方法区,在Class.newInstance的时候才会加载类中的代码块,但是Class.forName可以直接加载类中的静态代码块,然后我们来看下com.mysql.jdbc.Driver类:
类在被Class.forName的时候就加载了这一块代码块,将Driver类本身的对象注册进入DriverManager
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
* known to the {@code DriverManager}. If the driver is currently
* registered, no action is taken.
*
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
*/
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
可以看到注册的过程无非是把Driver对象存放进入了变量registeredDrivers中,这个变量原本是个List,具体不说这个变量了。
而当我们getConnection的时候:
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
public Connection connect(String url, Properties info)
throws SQLException
{
if(url != null)
{
if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://"))
return connectLoadBalanced(url, info);
if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://"))
return connectReplicationConnection(url, info);
}
Properties props = null;
if((props = parseURL(url, info)) == null)
return null;
if(!"1".equals(props.getProperty("NUM_HOSTS")))
return connectFailover(url, info);
try
{
com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);
return newConn;
}
catch(SQLException sqlEx)
{
throw sqlEx;
}
catch(Exception ex)
{
SQLException sqlEx = SQLError.createSQLException((new StringBuilder()).append(Messages.getString("NonRegisteringDriver.17")).append(ex.toString()).append(Messages.getString("NonRegisteringDriver.18")).toString(), "08001", null);
sqlEx.initCause(ex);
throw sqlEx;
}
}
至此整个加载流程应该已经清楚了,在Class.forName的过程中执行了静态代码块,在静态代码块中注册了Driver对象,在getConnection中取出注册的Driver并调用其connect方法,在mysql中connect方法的返回值是com.mysql.jdbc.Connection类的对象了,但是我们在调用过程中依旧是用其接口定义,这里充分运用了面向对象多态的性质。
再描述一遍,jdbc的类族设计是由sum公司设计了一套接口,再由各个数据库公司实现接口,我们在调用的过程中只需要使用接口去定义,然后在加载Driver的过程中底层代码会给我们选择好接口真正的实现类,以此来实现真正的数据库连接,此后所有的方法,包括获取statement等等,都是由接口声明调用,但是底层返回的是接口实现类。用这种桥接的模式,我们可以很轻松地在不同的数据库连接中进行转化,只需要修改Driver加载的类,如果把加载类的声明放入配置文件中,更是不需要重新去编译,可以很方便地在不同数据库间进行转化。
其实这样的java设计规范由别的公司进行实现的设计非常多,比如j2ee规范,假设没有j2ee规范,每个web容器各有各的接口,那么当我们需要把tomcat下跑的代码移植到jboss下的时候就不得不把代码全部重写一遍,这无疑是很不合理的,所以接口虽然没有做出任何实现,但是其规范的作用在这浩瀚的IT世界里却是不可缺少的
代理模式
代理模式是一种结构型设计模式,它可以为其他对象提供一种代理以控制对这个对象的访问。
所谓代理,是指具有与被代理对象相同的接口的类,客户端必须通过代理与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理。
代理模式中的UML图如下:
代理模式中的角色:
1.抽象对象角色
声明了目标类及代理类对象的共同接口,这样在任何可以使用目标对象的地方都可以使用代理对象。
2.目标对象角色
定义了代理对象所代表的目标对象。
3.代理对象角色
代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象和目标对象具有统一的接口,以便可以再任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或者之后,执行某些操作,而非单纯的将调用传递给目标对象。
下面给出示例代码:
首先定义AbstractObject类,在其中定义代理类和目标类共有的接口operation()
1 public abstract class AbstractObject {
2 protected abstract void operation();
3 }
下面定义目标实现类:
1 public class RealObject extends AbstractObject {
2 @Override
3 protected void operation() {
4 System.out.println("do operation...");
5 }
6 }
下面是代理类:
1 public class ProxyObject extends AbstractObject {
2 //对目标类的引用
3 private RealObject realObject;
4
5 public ProxyObject(RealObject realObject) {
6 this.realObject = realObject;
7 }
8
9 @Override
10 protected void operation() {
11 System.out.println("do something before real peration...");
12 if(realObject == null){
13 realObject = new RealObject();
14 }
15 realObject.operation();
16 System.out.println("do something after real operation...");
17 }
18 }
下面是测试类:
1 public class ProxyTest {
2 public static void main(String[] args) {
3 AbstractObject proxy = new ProxyObject(new RealObject());
4 proxy.operation();
5 }
6 }
执行结果如下:
从这个例子可以看出,代理对象将客户端的调用委派给了目标对象,在调用目标对象之前及之后都可以执行某些特定的操作。
总结:
代理模式的使用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
(1)修改原有的方法来适应。显然这违反了“对扩展开放,对修改关闭”的原则。
(2)采用一个代理类调用原来的方法,且对产生的结果进行控制。这就是代理模式了。
使用代理模式可以将功能划分的更加清晰,有助于后期的维护。
以上所说的这种代理模式称为静态代理。
扩展:
Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的。Spring本身含有两大特性,一个是IOC,一个是AOP的支持。
IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对。依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现。
AOP是Aspect Oriented Programming的缩写,即面向切面编程。与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。
IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?——动态代理技术https://blog.csdn.net/dreamwbt/article/details/53421328
目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术。Java自带的JDK动态代理技术是需要接口的,而CGLIB则是直接修改字节码。
外观模式
Facade(外观)
意图:
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性:
当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
1. 外观模式概述
不知道大家有没有比较过自己泡茶和去茶馆喝茶的区别,如果是自己泡茶需要自行准备茶叶、茶具和开水,如图1(A)所示,而去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事,如图1(B)所示。
图1 两种喝茶方式示意图
在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大,如图2(A)所示;而引入外观类之后,客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度,如图2(B)所示。
图2 外观模式示意图
外观模式中,一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道。
外观模式定义如下:
外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 Facade Pattern: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. |
外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
2. 外观模式结构与实现
2.1 模式结构
外观模式没有一个一般化的类图描述,通常使用如图2(B)所示示意图来表示外观模式。图3所示的类图也可以作为描述外观模式的结构图:
图3 外观模式结构图
由图3可知,外观模式包含如下两个角色:
(1) Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
(2) SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
2.2 模式实现
外观模式的主要目的在于降低系统的复杂程度,在面向对象软件系统中,类与类之间的关系越多,不能表示系统设计得越好,反而表示系统中类之间的耦合度太大,这样的系统在维护和修改时都缺乏灵活性,因为一个类的改动会导致多个类发生变化,而外观模式的引入在很大程度上降低了类与类之间的耦合关系。引入外观模式之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可。从这一点来说,外观模式在一定程度上并不符合开闭原则,增加新的子系统需要对原有系统进行一定的修改,虽然这个修改工作量不大。
外观模式中所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统。子系统类通常是一些业务类,实现了一些具体的、独立的业务功能,其典型代码如下:
class SubSystemA
{
public void MethodA()
{
//业务实现代码
}
}
class SubSystemB
{
public void MethodB()
{
//业务实现代码
}
}
class SubSystemC
{
public void MethodC()
{
//业务实现代码
}
}
在引入外观类之后,与子系统业务类之间的交互统一由外观类来完成,在外观类中通常存在如下代码:
由于在外观类中维持了对子系统对象的引用,客户端可以通过外观类来间接调用子系统对象的业务方法,而无须与子系统对象直接交互。引入外观类后,客户端代码变得非常简单,典型代码如下: