在介绍设计模式的应用之前,首先需要理解一下UML类图
的绘制方法
UML类图
类图主要由类、接口和各种关系组成。
关系主要包括泛化关系、依赖关系、关联关系和实现关系。
类
类的名称应该是一个名词,类名应该准确清晰的反映出问题域中的概念。按照UML的约定,类的名称中的每个词的首字母应大写,且使用正体名称来表示可实例化的类,使用斜体名称表示抽象的类。
比如:
- 实体类:Book类、Children类
- 抽象类:Shape类
类的名称可以加上路径名称。如下图所示:
上图说明Order这个类来自Business包中,也可以在命名时写成如下的形式:Business::Order,其中::左边是包的名称,右边是类的名称。
接口
接口是在没有给出对象的实现和状态的情况下对对象行为的描述。接口包含操作但不包含属性,且它没有对外界可见的关联。
一个类可以实现一个或多个接口,从而支持接口所指定的操作。
接口可以使用下面两种形式来表示:
-
在接口名称上面声明
<<interface>>
-
使用一个
圆圈
代表接口
关系
- 依赖关系:表示类之间使用关系
- 泛化关系:表示一般和特殊关系
- 关联关系:表示对象之间结构关系
- 实现关系:表示规格说明和实现之间关系
依赖关系
可以这么理解,提供者
类中包含了一些消息,或是属性;而客户
类需要使用到提供者
类当中的属性
主要的情形为:
- 客户类的操作需要提供者类的参数
- 客户类的操作返回提供者类
- 客户类的操作在实现中使用提供类的对象
举个例子,人可以开车,车负责移动操作,人负责开车操作:
代码表示:
class Car{
public void Move(){
}
}
class Person{
public void Travel(Car car){
car.move(); } // 提供者类作为参数
public Car buyCar(float price){
} // 提供者类作为方法的返回类型
public void doSome(){
Car car; ... } // 提供者类作为方法中的一个变量
}
泛化关系
该关系是存在于一般元素和特殊元素之间的分类关系。其中,特殊元素与一般元素兼容,且还包含自己特有的信息。
泛化可以用于类、接口、用例、参与者、包、状态机以及其它模型元素,它描述的是“is a kind of”的关系:
泛化关系使用带空心三角形的箭头来表示,箭头从子类指向父类。
举个例子,车辆Vehicle与Truck之间的泛化关系可以表示为:
代码演示:
class Vehicle{
}
class Truck extends Vehicle{
}
关联关系
关联关系是一种结构关系,指出了一个事物的对象与另一个事物的对象之间的语义上的连接。
在UML中,关联关系用一条连接两个类的实线表示。
举个例子,下面的图中,“拥有”为关联的名称,“客户”为类Person的角色,“交通工具”为类car的角色。角色也可以有自己的可见性,“客户”和“交通工具”前的“+”即代表其可见性为公有,关联端的“1”和“0…n”是描述的多重性:
关联关系又可以分为单向关联、双向关联、自关联、聚合关联和组合关联四种情形。
(1)单向关联
单向关联用带箭头的实线表示。箭头由源类指向目标类。
这种关联实际上是带导航的关联。它指在源类中要使用目标类的对象作为成员。
比如,教师归属于某个部门:
代码表示:
class Department{
}
class Teacher{
Department depart; // 和依赖关系不同的是,对于关联来说,源类中要有 以目标类为参数 的属性
}
(2)双向关联
双向关联关系不再绘制箭头,使用直线直接连接两个类即可,如下面两个类之间的关系即是双向关联关系:
代码表示:
class Category{
Film film;
}
class Film{
Category category;
}
(3)自关联
自关联关系即一个类与自己进行关联:
上面类关系的意思是employee类中有一个为employee类型的leader成员,表示员工的领导。
代码表示:
public class employee{
private employee leader;
}
(4)聚合关联
它表示整体与部分关系的关联,关联关系中一组元素组成了一个更大、更复杂的单元,它描述了“has a ”的关系。
在UML中聚合关系用带空心菱形头的实线来表示,头部指向整体。
聚合的双向关联的一个例子:
它表示College类是University类的一个聚合成分。
代码举例:
class University{
pubilc List<College> colleges;
}
class College{
public University university;
}
实现关系
实现关系不仅使用于接口和类之间,也可以是类的不同等级之间的联系,如粗设计与细设计之间。
实现一般使用带空心三角形的虚线箭头表示,箭头指向接口。
简单理解:实现关系是实现接口,泛化是继承抽象对象。
举个例子:IUser是一个接口,VipUser是一个类,VipUser类要实现接口IUser。
代码演示:
interface IUser{
public void walk();
}
class VipUser implements IUser{
public void walk();
}
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点,该模式的关键是将类的构造方法设置为private权限,并提供一个返回它的唯一实例的类方法,所以这个模式的结构中只包括一个角色,即单件类(Singleton)
类图
现在给出单例模式的类图:
单例模式的类图是自关联的,这也就意味着他的代码结构是这样的:
public class Singleton{
// 在自己类内创建一个对象属性
private Singleton uniqueInstance;
}
举例
设计一个JDBC连接数据库的单例
代码:
public class CDatabase{
//JDBC 连接过程
private static Connection conn = null;
private static CDatabase db = null;
// 将构造函数私有化
private CDatabase() throws SQLException{
if ( conn == null ){
DriverManager.registerDriver(new org.postgresql.Driver());
conn=DriverManager.getConnection("jdbc:postgresql://127.0.0.1:5432/ppmdb2","", "");
}
}
public synchronized static CDatabase getInstance() throws SQLException {
if ( db == null ){
// 外部通过getInstance()方法去创建实例
db = new CDatabase();
}
return db;
}
}
观察者模式
(别名:依赖,发布—订阅)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
在许多设计中,经常涉及到多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化。
因此,观察者模式的结构中包含四种角色:
- 主题(Subject)
- 观察者(Observer)
- 具体主题(ConcreteSubject)
- 具体观察者(ConcreteObserver)
具体主题和具体观察者是松耦合关系。由于主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题只是知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者只是知道它依赖的主题是实现主题(subject)接口的某个类的实例,但不需要知道具体是哪个类。
观察模式满足“开-闭原则”。主题(Subject)接口仅仅依赖于观察者(Observer)接口,这样,我们就可以让创建具体主题的类也仅仅是依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题(Observer)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码。
类图
分析:
- 观察者模式既然又叫做发布—订阅模式,那么一定会有一个
消息的发布者
和订阅者
,为了抽离,将发布与订阅设定为一个接口。然后我们通过具体的·ConcreteSubject和
ConcreteObserver`来实现接口,负责创建实体类。 - 由于发布者可以通过将接收者作为参数的方法来执行添加订阅者,所以
Subject
和Observer
之间是依赖关系 - 但是由于接口中无法添加属性,订阅者又需要知道自己订阅的信息更新了,所以需要创建一个Subjcet属性负责接收更新信息,所以
ConcreteObserver
和Subject
之间是单向关联关系
举例
举个例子:
有一个大学毕业生和一个归国留者都希望能及时知道“求职中心”最新的职业需求信息。
分析:
我们现在有一个消息发布者:求职中心
还有两个消息订阅者:大学毕业生、归国留者
所以我们在这里使用观察者模式来设计
JobCenter的代码:
public interface JobCenter{
public void addObserver(Jobseeker o);
public void deleteObserver(Jobseeker o);
public void notifyObservers();
}
JobSeeker的代码:
public interface JobSeeker{
public void hearTelephone(String heardMess);
}
ConcreteCenter的代码:
public class ConcreteCenter implements JobCenter{
String mess;
boolean changed;
ArrayList<JobSeeker> personList;
SeekJobCenter(){
personList=new ArrayList<JobSeeker>();
mess="";
changed=false;
}
public void addObserver(JobSeeker o){
if(!(personList.contains(o)))
personList.add(o);
}
public void deleteObserver(JobSeeker o){
if(personList.contains(o))
personList.remove(o);
}
// 具体的发布者类通过notifyObservers方法来通知具体的观察者
// 循环遍历所有的发布者信息
public void notifyObservers(){
if(changed){
for(int i=0;i<personList.size();i++){
JobSeeker observer=personList.get(i);
JobSeeker.hearTelephone(mess);
}
changed=false;
}
}
public void giveNewMess(String str){
if(str.equals(mess))
changed=false;
else{
mess=str;
changed=true;
}
}
}
UniversalStudent的代码:
public class UniversalStudent implements JobSeeker{
JobCenter jobCenter;
File myFile;
UniversalStudent(JobCenter subject,String fileName){
this.jobCenter=subject;
// 添加到该订阅者所对应的全局发布者身上
jobCenter.addObserver(this);
myFile = new File(fileName);
}
public void hearTelephone(String heardMess){
try{
RandomAccessFile out=new RandomAccessFile(myFile,"rw");
out.seek(out.length());
byte [] b=heardMess.getBytes();
out.write(b);
System.out.print("我是一个大学生,");
System.out.println("我向文件"+myFile.getName()+"写入如下内容:");
System.out.println(heardMess);
}
catch(IOException exp){
System.out.println(exp.toString());}
}
}
application的代码:
public class Application{
public static void main(String args[]){
JobCenter center = new JobCenter();
// 分别创建大学生类和海归类
UniversalStudent neko = new UniversalStudent(center,"A.txt");
GraduateStudent jijiang = new GraduateStudent(center,"B.txt");
// 设置消息
center.giveNewMess("mit需要10个java程序员。");
// 更新消息
center.notifyObservers();
center.giveNewMess("海景公司需要8个动画设计师。");
center.notifyObservers();
center.giveNewMess("仁海公司需要9个电工。");
center.notifyObservers();
center.giveNewMess("仁海公司需要9个电工。");
center.notifyObservers();
}
他们的行为顺序图如下:
享元模式
运用共享技术有效地支持大量细粒度的对象。
这句话可能不太好理解,结合应用来说,它最典型的应用就是池技术,比如数据库连接池
、线程池
等等。
一个类中的成员变量表明该类所创建的对象所具有的属性,在某些程序设计中我们可能用一个类创建若干个对象,但是我们发现这些对象的一个共同特点是它们有一部分属性的取值必须是完全相同的。
比如,当用Car类创建若干个同型号的轿车时,要求这些轿车的height, width, length值都必须是相同的,而color, power可以是不同的。
模式的结构中包括三种角色:
- 享元接口:定义了享元对外公开其内部数据的方法,以及享元接受外部数据的方法
- 具体享元 :具体享元类的成员变量为享元对象的内部状态;享元类的构造方法必须是private的
- 享元工厂:负责创建和管理享元对象
类图
举例1
创建若干辆奥迪A6和奥迪A4轿车。奥迪A6轿车的长、宽和高都是相同的,颜色和功率可以不同;奥迪A4轿车的长、宽和高都是相同的,颜色和功率可以不同。
分析:长宽高相同,颜色和功率不同,所以我们可以将长宽高的信息放入享元中
这样设计的话,可以节省内存开销,车与车之间共享CarData
类的一个实例,而且系统可以做到让Car
类无权修改CarData
中的数据。
回到这个问题,在这个问题中,应当使用享元对象封装轿车的长宽高,而颜色、功率作为享元对象的外部数据,这些外部数据可以传递给享元对象的方法的参数,因此,给出享元接口FlyWeight不仅包含有返回内部数据的方法,也包含有获得外部数据的方法。
在这里我将享元对象ConcreteFlyweight
设置为享元工厂的内部类,这样保证了享元对象与所处的环境无关,用户程序不允许直接使用具体享元,创建和管理享元对象全部由享元工厂负责。
举例2
享元模式还可以用来解释数据库连接池的原理。
这一部分的举例内容转载自这篇博客
这次我们采取技术演进的方式来谈谈数据库连接池的技术出现过程及其原理,以及当下最流行的开源数据库连接池jar包。
一.早期我们怎么进行数据库操作
1.原理:一般来说,Java应用程序访问数据库的过程是:
①装载数据库驱动程序;
②通过jdbc建立数据库连接;
③访问数据库,执行sql语句;
④断开数据库连接。
2.代码:
Public void FindAllUsers(){ //1、装载sqlserver驱动对象 DriverManager.registerDriver(new SQLServerDriver()); //2、通过JDBC建立数据库连接 Connection con =DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123"); //3、创建状态 Statement state =con.createStatement(); //4、查询数据库并返回结果 ResultSet result =state.executeQuery("select * from users"); //5、输出查询结果 while(result.next()){ System.out.println(result.getString("email")); } //6、断开数据库连接 result.close(); state.close(); con.close(); }
3.分析:
程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
上述的用户查询案例,如果同时有1000人访问,就会不断的有数据库连接、断开操作:
通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。原理如下:
二. 技术演进出来的数据库连接池
由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:
① 编写class 实现DataSource 接口
② 在class构造器一次性创建10个连接,将连接保存LinkedList中
③ 实现getConnection 从 LinkedList中返回一个连接
④ 提供将连接放回连接池中方法
1、连接池代码
// 创建连接池,连接池作为享元工厂 public class MyDataSource implements DataSource { //链表 --- 实现栈结构 privateLinkedList<Connection> dataSources = new LinkedList<Connection>(); //初始化连接数量 publicMyDataSource() { //一次性创建10个连接 for(int i = 0; i < 10; i++) { try { //1、装载sqlserver驱动对象 DriverManager.registerDriver(new SQLServerDriver()); //2、通过JDBC建立数据库连接 Connection con =DriverManager.getConnection( "jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123"); //3、将连接加入连接池中 dataSources.add(con); } catch (Exception e) { e.printStackTrace(); } } } @Override publicConnection getConnection() throws SQLException { //取出连接池中一个连接 finalConnection conn = dataSources.removeFirst(); // 删除第一个连接返回 returnconn; } //将连接放回连接池 publicvoid releaseConnection(Connection conn) { dataSources.add(conn); } }
2、使用连接池重构我们的用户查询函数
//查询所有用户 Public void FindAllUsers(){ //1、使用连接池建立数据库连接 MyDataSource dataSource = new MyDataSource(); Connection conn =dataSource.getConnection(); //2、创建状态 Statement state =con.createStatement(); //3、查询数据库并返回结果 ResultSet result =state.executeQuery("select * from users"); //4、输出查询结果 while(result.next()){ System.out.println(result.getString("email")); } //5、断开数据库连接 result.close(); state.close(); //6、归还数据库连接给连接池 dataSource.releaseConnection(conn); }
这就是数据库连接池的原理,它大大提供了数据库连接的利用率,减小了内存吞吐的开销。我们在开发过程中,就不需要再关心数据库连接的问题,自然有数据库连接池帮助我们处理,这回放心了吧。但连接池需要考虑的问题不仅仅如此,下面我们就看看还有哪些问题需要考虑。
装饰模式
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活:每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。
装饰模式是动态地扩展一个对象的功能,而不需要改变原始类代码的一种成熟模式。在装饰模式中,“具体组件”类和“具体装饰”类是该模式中的最重要的两个角色。
装饰模式的结构中包括四种角色:
- 抽象组件(Component)
- 具体组件(ConcreteComponent)
- 装饰(Decorator)
- 具体装饰(ConcreteDecotator)
类图
分析:
- 被装饰者和装饰者是松耦合关系。由于装饰(Decorator)仅仅依赖于抽象组件(Component),因此具体装饰只知道它要装饰的对象是抽象组件的某一个子类的实例,但不需要知道是哪一个具体子类。
- 装饰模式满足“开-闭原则”。不必修改具体组件,就可以增加新的针对该具体组件的具体装饰
- 可以使用多个具体装饰来装饰具体组件的实例
其实大致上来看,一个抽象的装饰和具体的对象组件可以看作是等价的,对象组件目的是生成一个对象,而装饰器目的是将抽象组件进行一次封装。
举例
假设系统中有一个Bird抽象类以及Bird类的一个子类Sparrow。Sparrow类实现了Bird类的fly方法,使得Sparrow类创建的对象(麻雀)调用fly方法能连续飞行100米。
现在,用户需要两只鸟,无论哪种鸟都可以,但必须分别能连续飞行150米和200米
分析:
-
无论那种鸟都可以,所以我们只需要在原来飞100米的基础上,添加装饰方法飞行150米,200米即可。
-
后面调用装饰者麻雀时采用代码:
Bird sparrow = new Sparrow(); // 生成一只普通的麻雀 Bird sparrowDecorator1 = new SparrowDecorator(sparrow); // 装饰麻雀
顺序图:
策略模式
策略模式是一种定义一系列算法的方法,所有的这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类之间的耦合。
说人话就是,某个对象的行为,在不同的场景中有不同的实现方式,这样就可以将这些实现方式定义成一组策略(比如很多个if-else判断语句),每个实现类对应一个策略;在不同的场景使用不同的实现类。
策略模式是处理算法的不同变体的一种成熟模式,策略模式通过接口或抽象类封装算法的标识,即在接口中定义一个抽象方法,实现该接口的类将实现接口中的抽象方法。
在策略模式中,封装算法标识的接口称作策略,实现该接口的类称作具体策略。
适用场景:
- 一个类定义了多种行为,并且这些行为在这个类中以多个条件语句的形式出现,可以使用策略模式避免在类中使用大量的条件语句。
- 程序不需要暴露复杂的、与算法相关的数据结构,可以使用策略模式封装算法。
- 需要使用一个算法的不同变体
策略模式的结构中包括三种角色:
- 策略(Strategy)
- 具体策略(ConcreteStrategy)
- 上下文(Context)
类图
分析:
- 某种策略有很多种处理方式,所以创建一个接口
Strategy
,具体的策略继承自这个策略接口 - 上下文通过判断传参,就可以生成该策略下的方案
举例1
比如某商场要根据金额的范围,采用不同的存款利息计算方式。
也许你会想用装饰模式,对于不同的计算方式,创建不同的利息计算类就可以了,但是这样还是很复杂,如果有很多种策略,这样的代码还是需要很多的if-else来判断最终要执行哪一个策略,很臃肿,所以采用策略模式来解决:
举例2
在某种比赛中有若干个裁判,每位裁判给选手一个得分。选手的最后得分是根据全体裁判的得分计算出来的。请给出几种计算选手得分的评分方案,对于某次比赛,可以从你的方案中选择一种方案作为本次比赛的评分方案。
分析:
关键词:多种方案,所以我们需要采用策略模式
具体应用代码:
public class Application{
public static void main(String args[]){
Context game = new Context();
game.setStrategy(new Strategy1());
Person zhang=new Person();
zhang.setName("张三");
double [] a={
9.12,9.25,8.87,9.99,6.99,7.88};
Person li=new Person();
li.setName("李四");
double [] b={
9.15,9.26,8.97,9.89,6.97,7.89};
zhang.setScore(game.getPersonScore(a));
li.setScore(game.getPersonScore(b));
System.out.println("使用算术平均值方案:");
System.out.println("得分:",zhang.getName(),zhang.getScore());
System.out.println("得分:%5.3f%n",li.getName(),li.getScore());
}}
举例3
把数组导出为表格的算法,输入一个数组,导出一个表格,当用户想改变导出的表格时,便可以通过改变输出的算法改变输出的结果。如果输出的内容用以网页显示,则输出
<table>JackMayaMikesShadow
如果输出的结果用以直接的屏幕输出,则可以输出:
±—±—±----±-----+
|Jack|Maya|Mikes|Shadow|
±—±—±----±-----+
.
分析:
首先写出公共的策略接口:
public interface TableExporter{
public String getExported(String[] data);
}
以网页形式输出:
public class HtmlExporter implements TableExporter{
public String getExported(String[] data){
if(data == null){
return "";
}
StringBuffer sb = new StringBuffer();
sb.append("<table>");
for(int i=0;i<data.length;i++)
sb.append("<tr><td>"+data[i]+"</td></tr>");
sb.append("</table>");
return sb.toString();
}
}
以字符串模拟表格形式输出:
public class LineExporter implements TableExporter{
public String getExported(String[] data){
if(data == null){
return "";
}
StringBuffer top = new StringBuffer("+");
StringBuffer mid = new StringBuffer("|");
for(int i =0;i<data.length;i++){
String str = data[i];
for(int j=0;j<str.length();j++)
top.append("-");
mid.append(str);
top.append("+");
mid.append("|");
}
top.append("\n");
mid.append("\n");
return top.toString()+mid.toString()+top.toString();
}
}
创建上下文对象,在这个上下文对象中设置策略:
public class Context{
TableExporter t;
public void setExporter(TableExporter t){
this.t = t;
}
public String getExported(String[] data){
return t. getExported(data);
}
}
主程序:
public class Client{
public static void main(String[] args){
String[] data = new String[]{
"Jack","Maya","Mikes","Shadow"};
Context c = new Context();
TableExporter tb = new HtmlExporter();
// 上下文将策略类作为参数,来设置最终选择的策略
c.setExporter(tb);
System.out.println(c.getExported(data));
}
}
代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式可以屏蔽用户真正请求的对象,使用户程序和真正的对象之间解耦
使用代理来担当那些创建耗时的对象的替身
模式的结构中包括三种角色:
- 抽象主题(Subject)
- 实际主题(RealSubject)
- 代理(Proxy)
类图
分析:
- 不管是实体对象还是代理对象,他们都是基于
抽象的主题类
的 - 代理对象可以理解为对实体对象的封装,隔断用户和实体对象之间的连接关系
举例
计算三角形的面积,并且需要做出合法性判断
分析:
- 首先找出主题类:
public interface Geometry{
public double getArea();
}
-
编写具体的三角形类:
public class Triangle implements Geometry{ double sideA,sideB,sideC,area; public Triangle(double a,double b,double c) { sideA=a; sideB=b; sideC=c; } public double getArea(){ double p=(sideA+sideB+sideC)/2.0; area=Math.sqrt(p*(p-sideA)*(p-sideB)*(p-sideC)) ; return area; } }
-
为了合法性监测,创建一个代理类,在代理类种创建三角形,并且做边长判断:
public class TriangleProxy implements Geometry{ double sideA,sideB,sideC; Triangle triangle; public void setABC(double a,double b,double c) { sideA=a; sideB=b; sideC=c; } public double getArea(){ if(sideA+sideB>sideC&&sideA+sideC>sideB&&sideB+sideC>sideA){ triangle=new Triangle(sideA,sideB,sideC); double area=triangle.getArea(); return area; } else return -1; } }
-
应用主体:
import java.util.Scanner; public class Application{ public static void main(String args[]) { Scanner reader=new Scanner(System.in); System.out.println("请输入三个数,每输入一个数回车确认"); double a=-1,b=-1,c=-1; a=reader.nextDouble(); b=reader.nextDouble(); c=reader.nextDouble(); TriangleProxy proxy=new TriangleProxy(); proxy.setABC(a,b,c); double area=proxy.getArea(); System.out.println("面积是:"+area); } }
绘图:
中介者模式
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式是封装一系列的对象交互的成熟模式,其关键是将对象之间的交互封装在称作中介者的对象中,中介者使各对象不需要显示地相互引用,这些对象只包含中介者的引用。当系统中某个对象需要和系统中另外一个对象交互时,只需将自己的请求通知中介者即可。
比如,在飞机塔台附近有三架飞机准备降落,这三架飞机可以相互沟通来决定谁先降落,他们也可以都和塔台沟通,然后塔台分别通知降落次序,中介者的接入可以让对象之间减少沟通依赖。
所以,中介者模式常常用于解决:
- 一些对象想互相通信,但又无法相互包含对方的引用
- 具体中介者集中了同事之间是如何交互的细节,使得系统比较清楚地知道整个系统中的同事是如何交互
- 具体中介者使得各个具体同事完全解耦,修改任何一个具体同事的代码不会影响到其他同事
模式的结构中包括四种角色:
- 中介者(Mediator)
- 具体中介者(ConcreteMediator)
- 同事(Colleague)
- 具体同事(ConcreteColleague)
类图
如果有多个中介者,就需要创建这个中介者接口;
但如果只有一个中介者,就不需要设计接口了
可能这样更容易理解中介者的价值:
举例
古代相互交战的A,B,C三方,想通过一个中介者调停之间的战火。A方委托调停者转达的信息是:“要求B方归还曾抢夺的100斤土豆,要求C方归还曾抢夺的20头牛”;B方委托调停者转达的信息是:“要求A方归还曾抢夺的10只鸡,要求C方归还曾抢夺的15匹马”;C方委托调停者转达的信息是:“要求A方归还曾抢夺的300斤小麦,要求B方归还曾抢夺的50头驴”
分析:
- 首先分析主体:三个具体同事(交战者),一个中介者
- 具体的交战者和具体的中介者之间有通信
- 三个具体同时可以抽象为一个同事接口
类图:
这里给出部分代码:
具体的中介者:
public class ConcreteMediator{
// 将具体的同事作为自己的属性
ColleagueA colleagueA;
ColleagueB colleagueB;
ColleagueC colleagueC;
public void registerColleagueA(ColleagueA colleagueA){
this.colleagueA=colleagueA;
}
public void registerColleagueB(ColleagueB colleagueB){
this.colleagueB=colleagueB;
}
public void registerColleagueC(ColleagueC colleagueC){
this.colleagueC=colleagueC;
}
public void deliverMess(Colleague colleague,String [] mess){
if(colleague==colleagueA){
if(mess.length>=2){
// 具体的同事接收到消息
colleagueB.receiverMess(colleague.getName()+mess[0]);
colleagueC.receiverMess(colleague.getName()+mess[1]);
}
}
else if(colleague==colleagueB){
if(mess.length>=2){
colleagueA.receiverMess(colleague.getName()+mess[0]);
colleagueC.receiverMess(colleague.getName()+mess[1]);
}
}
else if(colleague==colleagueC){
if(mess.length>=2){
colleagueA.receiverMess(colleague.getName()+mess[0]);
colleagueB.receiverMess(colleague.getName()+mess[1]);
}
}
}
}
具体的同事(交战者):
public class ColleagueA implements Colleague{
// 将一个具体的中介者作为自己的属性
ConcreteMediator mediator;
String name;
ColleagueA(ConcreteMediator mediator){
this.mediator=mediator;
mediator.registerColleagueA(this);
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void giveMess(String [] mess){
// 传递给中介者消息
mediator.deliverMess(this,mess);
}
public void receiverMess(String mess){
System.out.println(name+"收到的信息:");
System.out.println("\t"+mess);
}
}
顺序图:
状态模式
一个对象的状态依赖于它的变量的取值情况,对象在不同的运行环境中,可能具有不同的状态。在许多情况下,对象调用方法所产生的行为效果依赖于它当时的状态。
状态模式的关键是将对象的状态封装成为独立的类,对象调用方法时,可以委托当前对象所具有的状态调用相应的方法,使得当前对象看起来好像修改了它的类。
模式的结构中包括三种角色:
- 环境(Context)
- 抽象状态(State)
- 具体状态(Concrete State)
类图
如果你是从上往下阅读,你会发现状态模式和策略模式的UML图看起来十分类似,甚至感觉可以通用。
所以在这里我用一个例子来区分他俩:
- 状态模式好比于:你在食堂买饭,付款前、付款后、打饭途中,这三个状态都是针对于买饭这一状态的不同处理方法
- 策略模式好比于:你在食堂买饭,是用现金、微信还是支付宝,使用的是不同的支付策略
重要区别:
- 状态模式重点在各状态之间的切换,从而做不同的事情;而策略模式更侧重于根据具体情况选择策略,并不涉及切换
- 状态模式不同状态下做的事情不同,而策略模式做的都是同一件事
举例1
设计带文字提示信息的温度计。带文字提示信息的温度计可以根据用户的需求显示某些提示信息,比如用户希望温度在0度以下时,显示提示信息为“低温温度”,温度在10-26度之间时,显示提示信息为“正常温度”,当温度在大于39度时,显示提示信息为“高温温度”
分析:
- 一共有三种温度状态
- 再不同状态下温度计的显示不同
类图:
主程序:
public class Application{
public static void main(String args[]) {
// 具体的状态
TemperatureState state=new LowState(-12);
// 在这道题中 温度计就相当于环境
Thermometer thermometer=new Thermometer();
thermometer.setState(state);
thermometer.showMessage();
state=new MiddleState(20);
thermometer.setState(state);
thermometer.showMessage();
state=new HeightState(39);
thermometer.setState(state);
thermometer.showMessage();
}
}
举例2
打电话时,可能出现这几种情况:用户开机,用户关机,用户欠费停机,用户消户等。 所以当我们拨打这个号码的时候:系统就要判断,该用户是否在开机且不忙状态,又或者是关机,欠费等状态。但不管是那种状态我们都应给出对应的处理操作。
分析:
如果没有使用状态模式,代码会是这样的:
public class MobileTelephone {
// 开机且用户没使用的状态
private int waitState = 1;
// 关机状态
private int closeState = 2;
//停机状态
private int stopState = 3;
// 已消户
private int logoutState = 4;
// 拨通
private int connectState = 5;
//当前状态
private int state = openState;
//打电话操作
public void call() {
if (state == waitState ) {
system.out.println(“连接成功,可以通话”);
//改变状态为连接通话状态
changeState(connectState);
}else if(state == closeState ) {
system.out.println("用户已关机");
}else if(state == stopState){
system.out.println("用户欠费停机");
}else if (state == connectState){
system.out.println("用户正在通话,请稍后再拨!");
}
}
//通话计时
public void countTime(){
if (state == connectState ){
system.out.println("计时处理。。。");
system.out.println("通话结束,计时结束");
changeState(waitState);
}else if(state = waitState){
system.out.println("电话不在连接状态,不计时。。。");
}else if(state == closeState){
system.out.println("用户已关机");
}else if(state == stopState){
system.out.println("用户欠费停机");
}
}
public void changeState(int concreteState) {
this.state = concreteState;
}
}
使用状态模式:
首先定义上下文Context,在这个对象里面可以更改状态,并实现一些操作:
public class MobileTelephone {
//开机且空闲状态
private State waitState = new waitState();
//通话连接状态
private State connectState = new ConnectState();
//关机状态
private State stopState = new stopState();
//当前状态
private State state = waitState;
//拨打操作
public void call() {
state.call();
}
//计时操作
public void countTimes() {
state.countTimes();
}
public void chageState(State concreteState) {
this.state = concreteState;
}
}
定义状态接口:
public interface IState {
void call();
void countTimes();
// 其他操作。
}
实现具体的状态:
public class WaitState implements IState {
private Context context;
public void call() {
//做连接操作
system.out.println("连接成功,可以通话");
//改变状态为连接通话状态
context.changeState(context.getConnectState());
}
public void countTimes() {
system.out.print("电话不在连接状态,不能计时");
}
}
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦。
工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。
简单来说就是:用户不需要管子类创建的过程,直接调用子类的方法就是了
模式的结构中包括四种角色:
- 抽象产品(Product)
- 具体产品(ConcreteProduct)
- 构造者(Creator)
- 具体构造者(ConcreteCreator)
类图
分析:
- 抽象的构造者类,和产品接口
- 通过具体的构造者来生产一件产品,而该产品的细节由产品类来决定
- 构造者无需考虑产品的生产细节,它只需要说:哦,我现在要生产这件产品。随后系统就为他创建好了目标产品类
举例1
有一个PenCore类(笔芯),该类是一个抽象类。假设PenCore类有三个子类,分别是RedPenCore、BluePenCore和BlackPenCore,而系统设计的目的是为用户提供BallPen类(圆珠笔)的子类的实例,即含有笔芯的圆珠笔,也就是说系统想让用户使用BallPen类的子类的实例来得到PenCore类的子类的实例。
分析:
其实在我看来(不一定准确),
- 工厂方法模式主要用来解决:我要创建很多不同种类的对象
- 状态模式主要用来解决:我要处理对象的不同状态
- 策略模式主要用来解决:我要罗列出需要的所有方法
在这道题里面,我们最终需要的是圆珠笔,每只圆珠笔的笔芯不需要我们考虑是如何生产的,所以采用工厂方法模式。
类图:
顺序图:
代码:
抽象的构造者(圆珠笔类):
public abstract class BallPen{
BallPen(){
System.out.println("生产了一只装有"+getPenCore().color+"笔芯的圆珠笔");
}
public abstract PenCore getPenCore(); //工厂方法
}
具体的构造者(使用了某种类型笔芯的圆珠笔)
public class RedBallPen extends BallPen{
public PenCore getPenCore(){
// 和具体的笔芯产品类之间是依赖关系,因为这个方法采用的是PenCore类型的返回值
return new RedPenCore();
}
}
抽象的产品(笔芯类):
public abstract class PenCore{
String color;
public abstract void writeWord(String s);
}
具体的产品(某根确定了颜色的笔芯):
public class RedPenCore extends PenCore{
RedPenCore(){
color="红色";
}
public void writeWord(String s){
System.out.println("写出"+color+"的字:"+s);
}
}
举例2
系统目前已经按着有关药品的规定设计了一个抽象类Drug,该抽象类规定了所创建的药品必须给出药品的成分及其含量。Drug目前有两个子类:Paracetamol和Amorolfine。 Paracetamol负责创建氨加黄敏一类的药品,Amorolfine负责创建盐酸阿莫罗芬一类的药品。
一个为某药店开发的应用程序需要使用Drug类的某个子类的实例为用户提供药品,但是药店的应用程序不能使用Drug的子类的构造方法直接创建对象,因为药店没有能力给出药品的各个成分的含量,只有药厂才有这样的能力。
分析:
- 划重点:药店的应用程序不能使用Drug的子类的构造方法直接创建对象,由于用户无法构造具体的细节,这里采用工厂方法模式。
- 用户使用的类和Drug类不同,用户只需要提供药品,并不关心具体药品是如何生产出来的。
类图:
代码以一个具体药店的药来举例:
public class PharmacyParacetamol implements PharmacyDrug{
public Drug getDrug(){
int [] a = {
250,15,1,10};
Drug drug = new Paracetamol("氨加黄敏胶囊",a);
return drug;
}
}
组合模式
将对象组合成数形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
组合模式是关于怎样将对象形成树形结构来表现整体和部分的层次结构的成熟模式。使用组合模式,可以让用户以一致的方式处理个体对象和组合对象,组合模式的关键在于无论是个体对象还是组合对象都实现了相同的接口或都是同一个抽象类的子类。
模式的结构中包括三种角色:
- 抽象组件(Component)
- Composite节点(Composite Node)
- Leaf节点(Leaf Node)
类图
举例1
用组合模式描述连队的军士结构,并计算军饷。
一个连队由1个连长,2个排长,6个班长和60个士兵构成。连长直接指挥2个排长,每个排长直接指挥3个班长,每个班长直接指挥10个士兵。
连长的军饷是每月5000元,排长4000元,班长2000元,士兵1000元。
使用组合模式,让连队的军士形成树形结构,并计算一个班的军饷,一个排的军饷和整个连队的军饷。
分析:
- 首先画出结构的树形结构
- 连长
- 排长
- 班长
- 10个士兵
- 班长
- 10个士兵
- 班长
- 10个士兵
- 班长
- 排长
- 班长
- 10个士兵
- 班长
- 10个士兵
- 班长
- 10个士兵
- 班长
- 排长
-
根据树形结构可以开始设计组合模式的结构图了
-
首先给出抽象组件接口,在这个接口里面定义了每位军士拥有的方法
import java.util.*; public interface MilitaryPerson{ public void add(MilitaryPerson person); public void remove(MilitaryPerson person); public MilitaryPerson getChild(int index); public Iterator<MilitaryPerson> getAllChildren(); public boolean isLeaf(); public double getSalary(); public void setSalary(double salary); }
-
设计每个叶子节点上士兵的方法与属性
import java.util.*; public class MilitarySoldier implements MilitaryPerson{ double salary; String name; MilitarySoldier(String name,double salary){ this.name=name; this.salary=salary; } // 士兵已经是叶子节点了,无法再添加或删除子节点 public void add(MilitaryPerson person) { } public void remove (MilitaryPerson person){ } public MilitaryPerson getChild(int index) { return null; } public Iterator<MilitaryPerson> getAllChildren() { return null; } public boolean isLeaf(){ return true; } public double getSalary(){ return salary; } public void setSalary(double salary){ this.salary=salary; } }
-
设计组合Composite节点
import java.util.*; public class MilitaryOfficer implements MilitaryPerson{ // 创建一个链表属性,可以存放树的结点 LinkedList<MilitaryPerson> list; String name; double salary; MilitaryOfficer(String name,double salary){ this.name=name; this.salary=salary; list=new LinkedList<MilitaryPerson>(); } public void add(MilitaryPerson person) { list.add(person); } public void remove(MilitaryPerson person){ list.remove(person); } public MilitaryPerson getChild(int index) { return list.get(index); } public Iterator<MilitaryPerson> getAllChildren() { return list.iterator(); } public boolean isLeaf(){ return false; } public double getSalary(){ return salary; } public void setSalary(double salary){ this.salary=salary; } }
-
按照题意计算总工资
import java.util.*; public class ComputerSalary{ public static double computerSalary(MilitaryPerson person){ double sum=0; if(person.isLeaf()==true){ sum=sum+person.getSalary(); } // 如果不是叶子结点 if(person.isLeaf()==false){ sum=sum+person.getSalary(); // 以类似于递归的方式获取到所有子节点的总工资 Iterator<MilitaryPerson> iterator=person.getAllChildren(); while(iterator.hasNext()){ MilitaryPerson p= iterator.next(); sum=sum+computerSalary(p);; } } return sum; } }
-
-
-
在这里用伪代码的形式表示一下树形结构的创建过程:
连长.add(排长1); 连长.add(排长2);
排长1.add(班长11); 排长1.add(班长12); 排长1.add(班长13);
排长2.add(班长21); 排长2.add(班长22); 排长2.add(班长23);
for(int i=0;i<=9;i++){
班长11.add(士兵[i]);
班长12.add(士兵[i+10]);
班长13.add(士兵[i+20]);
班长21.add(士兵[i+30]);
班长22.add(士兵[i+40]);
班长23.add(士兵[i+50]);
}
举例2
一幅画可以由一个个复杂的图案组成,而复杂的图案其实可以由一个个简单的图形组成
举例3
一个复杂的算式最终可以拆分为简单的数字运算
模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法是关于怎样将若干个方法集成到一个方法中,以便形成一个解决问题的算法骨架。模板方法模式的关键是在一个抽象类中定义一个算法的骨架,即将若干个方法集成到一个方法中,并称该方法为一个模板方法,或简称为模板。
简单来说,和泛型有点类似。
模式的结构中包括两种角色:
- 抽象模板(Abstract Template)
- 具体模板(Concrete Template)
类图
举例
抽象模板:
import java.io.*;
// 模板抽象类
public abstract class AbstractTemplate{
File [] allFiles;
File dir;
AbstractTemplate(File dir){
this.dir=dir;
}
public final void showFileName(){
allFiles=dir.listFiles();
sort();
printFiles();
}
// 定义抽象的方法等待子类实现
// 这样看来抽象模板类起到的作用就是:定义好这个算法的整体骨架,具体的实现由子类来完成
public abstract void sort();
public abstract void printFiles();
}
具体模板1:
public class ConcreteTemplate1 extends AbstractTemplate{
ConcreteTemplate1(File dir){
super(dir);
}
public void sort(){
for(int i=0;i<allFiles.length;i++)
for(int j=i+1;j<allFiles.length;j++)
if(allFiles[j].lastModified()<allFiles[i].lastModified()){
File file=allFiles[j];
allFiles[j]=allFiles[i];
allFiles[i]=file;
}
}
public void printFiles(){
for(int i=0;i<allFiles.length;i++){
long time=allFiles[i].lastModified();
Date date=new Date(time);
SimpleDateFormat matter= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str=matter.format(date);
String name=allFiles[i].getName();
int k=i+1;
System.out.println(k+" "+name+"("+str+")");
}
}
}
具体模板2:
public class ConcreteTemplate2 extends AbstractTemplate{
ConcreteTemplate2(File dir){
super(dir);
}
public void sort(){
for(int i=0;i<allFiles.length;i++)
for(int j=i+1;j<allFiles.length;j++)
if(allFiles[j].length()<allFiles[i].length()){
File file=allFiles[j];
allFiles[j]=allFiles[i];
allFiles[i]=file;
}
}
public void printFiles(){
for(int i=0;i<allFiles.length;i++){
long fileSize=allFiles[i].length() ;
String name=allFiles[i].getName();
int k=i+1;
System.out.println(k+" "+name+"("+fileSize+" 字节)");
}
}
}
应用:
import java.io.File;
public class Application{
public static void main(String args[]) {
File dir=new File("d:/javaExample");
// 多态
AbstractTemplate template=new ConcreteTemplate1(dir);
System.out.println(dir.getPath()+"目录下的文件:");
// 执行模板方法模式的核心
// 父类通过定义一个final方法让子类无法重写
template.showFileName();
template=new ConcreteTemplate2(dir);
System.out.println(dir.getPath()+"目录下的文件:");
template.showFileName();
}
}