天天看点

Java经典设计模式:七大结构型模式(附实例和详解)



总体来说设计模式分为三大类:创建型模式、结构型模式和行为型模式。

博主的上一篇文章已经提到过创建型模式,此外该文章还有设计模式概况和设计模式的六大原则。设计模式的六大原则是设计模式的核心思想,详情请看博主的另外一篇文章: Java经典设计模式之五大创建模式(附实例和详解)。

接下来我们看看结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中适配器模式主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。其中的对象的适配器模式是各种结构型模式的起源。

一、适配器模式

适配器模式主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。有点抽象,我们来看看详细的内容。

1.1、类的适配器模式

类的适配器模式核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。

1 2 3 4 5 6 7

package

com.model.structure;

public

class

Source {

public

void

method1() { 

System.out.println(

"this is original method!"

); 

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

interface

Targetable {

public void method1();

public

void

method2();

}

1 2 3 4 5 6 7

package

com.model.structure;

public

class

Adapter

extends

Source

implements

Targetable {

public

void

method2() {

System.out.println(

"this is the targetable method!"

);

}

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

AdapterTest {

public

static

void

main(String[] args) {

Targetable target =

new

Adapter();

target.method1();

target.method2();

}

}

AdapterTest的运行结果:

Java经典设计模式:七大结构型模式(附实例和详解)

1.2、对象的适配器模式

对象的适配器模式的基本思路和类的适配器模式相同,只是将Adapter类作修改成Wrapper,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

package

com.model.structure;

public

class

Wrapper

implements

Targetable {

private

Source source;

public

Wrapper(Source source) {

super

();

this

.source = source;

}

@Override

public

void

method2() {

System.out.println(

"this is the targetable method!"

);

}

@Override

public

void

method1() {

source.method1();

}

}

1 2 3 4 5 6 7 8 9 10

package

com.model.structure;

public

class

AdapterTest {

public

static

void

main(String[] args) {

Source source =

new

Source();

Targetable target =

new

Wrapper(source);

target.method1();

target.method2();

}

}

运行结果跟类的适配器模式例子的一样。

1.3、接口的适配器模式

接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行了。

这里看文字描述已经试够清楚的了,因此就不贴代码实例了。

二、装饰模式

装饰模式:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰模式的特点:

(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

(2) 装饰对象包含一个真实对象的引用(reference)

(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。

(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。继承不能做到这一点,继承的功能是静态的,不能动态增删。

具体看看代码实例

1 2 3 4 5

package

com.model.structure;

public

interface

Sourceable {

public

void

method();

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

Source

implements

Sourceable {

@Override

public

void

method() {

System.out.println(

"the original method!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

package

com.model.structure;

public

class

Decorator

implements

Sourceable {

private

Sourceable source;

public

Decorator(Sourceable source) {

super

();

this

.source = source;

}

@Override

public

void

method() {

System.out.println(

"before decorator!"

);

source.method();

System.out.println(

"after decorator!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

package

com.model.structure;

public

class

DecoratorTest {

public

static

void

main(String[] args) {

//(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。

//(2) 装饰对象包含一个真实对象的引用(reference)

//(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。

//(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。

//    在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

//    继承不能做到这一点,继承的功能是静态的,不能动态增删。

Sourceable source =

new

Source();

Sourceable obj =

new

Decorator(source);

obj.method();

}

}

运行结果:

1 2 3

before decorator!

the original method!

after decorator!

三、代理模式

代理模式就是多一个代理类出来,替原对象进行一些操作。代理类就像中介,它比我们掌握着更多的信息。

具体看看代码实例。

1 2 3 4 5

package

com.model.structure;

public

interface

Sourceable {

public

void

method();

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

Source

implements

Sourceable {

@Override

public

void

method() {

System.out.println(

"the original method!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

package

com.model.structure;

public

class

Proxy

implements

Sourceable {

private

Source source;

public

Proxy() {

super

();

this

.source =

new

Source();

}

@Override

public

void

method() {

before();

source.method();

atfer();

}

private

void

atfer() {

System.out.println(

"after proxy!"

);

}

private

void

before() {

System.out.println(

"before proxy!"

);

}

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

ProxyTest {

public

static

void

main(String[] args) {

Sourceable source =

new

Proxy();

source.method();

}

}

运行结果:

1 2 3

before proxy!

the original method!

after proxy!

四、外观模式

外观模式是为了解决类与类之间的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。

我们以一个计算机的启动过程为例,看看如下的代码:

1 2 3 4 5 6 7 8 9 10 11 12

package

com.model.structure;

public

class

CPU {

public

void

startup() {

System.out.println(

"cpu startup!"

);

}

public

void

shutdown() {

System.out.println(

"cpu shutdown!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12

package

com.model.structure;

public

class

Disk {

public

void

startup() {

System.out.println(

"disk startup!"

);

}

public

void

shutdown() {

System.out.println(

"disk shutdown!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12

package

com.model.structure;

public

class

Memory {

public

void

startup() {

System.out.println(

"memory startup!"

);

}

public

void

shutdown() {

System.out.println(

"memory shutdown!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

package

com.model.structure;

public

class

Computer {

private

CPU cpu;

private

Memory memory;

private

Disk disk;

public

Computer() {

cpu =

new

CPU();

memory =

new

Memory();

disk =

new

Disk();

}

public

void

startup() {

System.out.println(

"start the computer!"

);

cpu.startup();

memory.startup();

disk.startup();

System.out.println(

"start computer finished!"

);

}

public

void

shutdown() {

System.out.println(

"begin to close the computer!"

);

cpu.shutdown();

memory.shutdown();

disk.shutdown();

System.out.println(

"computer closed!"

);

}

}

1 2 3 4 5 6 7 8 9 10

package

com.model.structure;

public

class

User {

public

static

void

main(String[] args) {

Computer computer =

new

Computer();

computer.startup();

computer.shutdown();

}

}

运行结果:

1 2 3 4 5 6 7 8 9 10

start the computer!

cpu startup!

memory startup!

disk startup!

start computer finished!

begin to close the computer!

cpu shutdown!

memory shutdown!

disk shutdown!

computer closed!

五、桥接模式

在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。

在提出桥梁模式的时候指出,桥梁模式的用意是”将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。这句话有三个关键词,也就是抽象化、实现化和脱耦。

抽象化:存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待。

实现化:抽象化给出的具体实现,就是实现化。

脱耦:所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。

下面我们来看看代码实例:

1 2 3 4 5

package

com.model.structure;

public

interface

Driver { 

public

void

connect(); 

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

MysqlDriver

implements

Driver {

@Override

public

void

connect() {

System.out.println(

"connect mysql done!"

);

}

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

DB2Driver

implements

Driver {

@Override

public

void

connect() {

System.out.println(

"connect db2 done!"

);

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

package

com.model.structure;

public

abstract

class

DriverManager {

private

Driver driver;

public

void

connect() {

driver.connect();

}

public

Driver getDriver() {

return

driver;

}

public

void

setDriver(Driver driver) {

this

.driver = driver;

}

}

1 2 3 4 5 6 7 8 9

package

com.model.structure;

public

class

MyDriverManager

extends

DriverManager {

public

void

connect() {

super

.connect();

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

package

com.model.structure;

public

class

Client {

public

static

void

main(String[] args) {

DriverManager driverManager =

new

MyDriverManager();

Driver driver1 =

new

MysqlDriver();

driverManager.setDriver(driver1);

driverManager.connect();

Driver driver2 =

new

DB2Driver();

driverManager.setDriver(driver2);

driverManager.connect();

}

}

执行结果:

1 2

connect mysql done!

connect db2 done!

如果看完代码实例还不是很理解,我们想想如下两个维度扩展:(1)假设我想加一个OracleDriver,这是一个维度,很好理解,不多解释。(2)假设我们想在连接前后固定输出点什么,我们只需要加一个MyDriverManager2,代码如下:

1 2 3 4 5 6 7 8 9 10 11

package

com.model.structure;

public

class

MyDriverManager2

extends

DriverManager {

public

void

connect() {

System.out.println(

"before connect"

);

super

.connect();

System.out.println(

"after connect"

);

}

}

再将Client代码中的MyDriverManager 改成 MyDriverManager2 ,执行结果如下:

1 2 3 4 5 6

before connect

connect mysql done!

after connect

before connect

connect db2 done!

after connect

六、组合模式

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 “组合对象” 的含义。

组合模式让你可以优化处理递归或分级数据结构。

《设计模式》:将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

涉及角色:

Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。

Leaf:在组合中表示叶子结点对象,叶子结点没有子结点。

Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。

比如现实中公司内各部门的层级关系,请看代码:

Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

package

com.model.structure;

public

abstract

class

Company {

private

String name;

public

Company() {

}

public

Company(String name) {

super

();

this

.name = name;

}

public

String getName() {

return

name;

}

public

void

setName(String name) {

this

.name = name;

}

protected

abstract

void

add(Company company);

protected

abstract

void

romove(Company company);

protected

abstract

void

display(

int

depth);

}

Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

package

com.model.structure;

import

java.util.ArrayList;

import

java.util.List;

public

class

ConcreteCompany

extends

Company {

private

List<Company> cList;

public

ConcreteCompany() {

cList =

new

ArrayList();

}

public

ConcreteCompany(String name) {

super

(name);

cList =

new

ArrayList();

}

@Override

protected

void

add(Company company) {

cList.add(company);

}

@Override

protected

void

display(

int

depth) {

StringBuilder sb =

new

StringBuilder(

""

);

for

(

int

i =

; i < depth; i++) {

sb.append(

"-"

);

}

System.out.println(

new

String(sb) +

this

.getName());

for

(Company c : cList) {

c.display(depth +

2

);

}

}

@Override

protected

void

romove(Company company) {

cList.remove(company);

}

}

Leaf:在组合中表示叶子结点对象,叶子结点没有子结点。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

package

com.model.structure;

public

class

HRDepartment

extends

Company {

public

HRDepartment(String name) {

super

(name);

}

@Override

protected

void

add(Company company) {

}

@Override

protected

void

display(

int

depth) {

StringBuilder sb =

new

StringBuilder(

""

);

for

(

int

i =

; i < depth; i++) {

sb.append(

"-"

);

}

System.out.println(

new

String(sb) +

this

.getName());

}

@Override

protected

void

romove(Company company) {

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

package

com.model.structure;

public

class

FinanceDepartment

extends

Company {

public

FinanceDepartment(String name) {

super

(name);

}

@Override

protected

void

add(Company company) {

}

@Override

protected

void

display(

int

depth) {

StringBuilder sb =

new

StringBuilder(

""

);

for

(

int

i =

; i < depth; i++) {

sb.append(

"-"

);

}

System.out.println(

new

String(sb) +

this

.getName());

}

@Override

protected

void

romove(Company company) {

}

}

Client:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

package

com.model.structure;

public

class

Client {

public

static

void

main(String[] args) {

Company root =

new

ConcreteCompany();

root.setName(

"北京总公司"

);

root.add(

new

HRDepartment(

"总公司人力资源部"

));

root.add(

new

FinanceDepartment(

"总公司财务部"

));

Company shandongCom =

new

ConcreteCompany(

"山东分公司"

);

shandongCom.add(

new

HRDepartment(

"山东分公司人力资源部"

));

shandongCom.add(

new

FinanceDepartment(

"山东分公司账务部"

));

Company zaozhuangCom =

new

ConcreteCompany(

"枣庄办事处"

);

zaozhuangCom.add(

new

FinanceDepartment(

"枣庄办事处财务部"

));

zaozhuangCom.add(

new

HRDepartment(

"枣庄办事处人力资源部"

));

Company jinanCom =

new

ConcreteCompany(

"济南办事处"

);

jinanCom.add(

new

FinanceDepartment(

"济南办事处财务部"

));

jinanCom.add(

new

HRDepartment(

"济南办事处人力资源部"

));

shandongCom.add(jinanCom);

shandongCom.add(zaozhuangCom);

Company huadongCom =

new

ConcreteCompany(

"上海华东分公司"

);

huadongCom.add(

new

HRDepartment(

"上海华东分公司人力资源部"

));

huadongCom.add(

new

FinanceDepartment(

"上海华东分公司账务部"

));

Company hangzhouCom =

new

ConcreteCompany(

"杭州办事处"

);

hangzhouCom.add(

new

FinanceDepartment(

"杭州办事处财务部"

));

hangzhouCom.add(

new

HRDepartment(

"杭州办事处人力资源部"

));

Company nanjingCom =

new

ConcreteCompany(

"南京办事处"

);

nanjingCom.add(

new

FinanceDepartment(

"南京办事处财务部"

));

nanjingCom.add(

new

HRDepartment(

"南京办事处人力资源部"

));

huadongCom.add(hangzhouCom);

huadongCom.add(nanjingCom);

root.add(shandongCom);

root.add(huadongCom);

root.display(

);

}

}

运行结果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

北京总公司

--总公司人力资源部

--总公司财务部

--山东分公司

----山东分公司人力资源部

----山东分公司账务部

----济南办事处

------济南办事处财务部

------济南办事处人力资源部

----枣庄办事处

------枣庄办事处财务部

------枣庄办事处人力资源部

--上海华东分公司

----上海华东分公司人力资源部

----上海华东分公司账务部

----杭州办事处

------杭州办事处财务部

------杭州办事处人力资源部

----南京办事处

------南京办事处财务部

------南京办事处人力资源部

七、享元模式

享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。

看下数据库连接池的代码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

package

com.model.structure;

import

java.sql.Connection;

import

java.sql.DriverManager;

import

java.sql.SQLException;

import

java.util.Vector;

public

class

ConnectionPool {

private

Vector<Connection> pool;

private String url = "jdbc:mysql://localhost:3306/test";

private String username = "root";

private String password = "root";

private String driverClassName = "com.mysql.jdbc.Driver";

private int poolSize = 100;

private static ConnectionPool instance = null;

Connection conn = null;

private ConnectionPool() {

pool = new Vector<Connection>(poolSize);

for (int i = 0; i < poolSize; i++) {

try {

Class.forName(driverClassName);

conn = DriverManager.getConnection(url, username, password);

pool.add(conn);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

public synchronized void release() {

pool.add(conn);

}

public

synchronized

Connection getConnection() {

if

(pool.size() >

) {

Connection conn = pool.get(

);

pool.remove(conn);

return

conn;

}

else

{

return

null

;

}

}

}