Java常见的9种设计模式(二)
我们在上一章Java常见的9种设计模式(一)中认识了单例模式
、简单工厂模式
、工厂模式
、代理模式
这四种设计模式,今天继续来认识剩余的五种常见设计模式:
命令模式
、策略模式
、门面模式
、桥接模式
、观察者模式
。
命令模式
我们在日常的开发中,经常会根据业务逻辑进行一定的功能开发,但是有时候在完成一个功能开发时,会发现该功能的步骤充满了一些不确定性,只有当执行该方法时才能确定这个步骤如何完成,那么我们可以通过设计模式去规避这样的不确定性。
伪代码如下:
创建Command
接口,该接口我们可以把它称为命令接口
public interface Command {
void process(int[] target);
}
创建ProcessArray
类,该类用于完成对数组的处理
/**
* Created by lany on 2018/9/11.
*/
public class ProcessArray {
//定义一个each()方法,用于处理数组。
public void each(int[] target,Command cmd){
cmd.process(target);
}
}
创建TestCommand
类
/**
* Created by lany on 2018/9/11.
*/
public class TestCommand {
public static void main(String[] args){
ProcessArray processArray = new ProcessArray();
int[] target = {-1,2,3,5};
//实现处理过程
processArray.each(target, new Command() {
int sum = 0;
@Override
public void process(int[] target) {
//计算数组的和
for (int tar: target
) {
sum += tar;
}
System.out.println(sum);
}
});
}
}
在上述伪代码中,传入该方法的是一个对象,该对象通常是某个接口的匿名实现类的实例,该接口通常被称为命令接口,这种设计方式也被称为命令模式。
HibernateTemplate使用了executeXxx()方法弥补了HibernateTemplate的不足,该方法需要接受一个HibernateCallback接口
策略模式
策略模式用于封装系=系统中的算法,这些算法通常被封装在一个被称为Context
的类中,客户端程序可以自由选择其中一种算法,或让Context
为客户端选择一种最佳方法–使用策略模式的优势是为了支持算法的自由切换。
伪代码如下:
创建Operation
接口,抽象成操作策略接口
/**
* 策略接口
* Created by lany on 2018/9/3.
*/
public interface Operation {
float parameter(float a,float b);
}
创建Addtion
类,该类为策略接口的实现类,操作策略为加法
/**
* 加法具体策略类
* Created by lany on 2018/9/3.
*/
public class Addtion implements Operation {
@Override
public float parameter(float a, float b) {
return a+b;
}
}
创建Multiplication
类,该类为策略接口的实现类,操作策略为乘法
/**
* 乘法具体策略类
* Created by lany on 2018/9/3.
*/
public class Multiplication implements Operation {
@Override
public float parameter(float a, float b) {
return a*b;
}
}
创建OperationContext
类,用于定义具体的策略
**
* 操作策略定义
* Created by lany on 2019/4/10.
*/
public class OperationContext {
private Operation operation;
public OperationContext() {
}
//传入某种操作的策略接口
public OperationContext(Operation operation) {
this.operation = operation;
}
//根据传入的操作策略对数值进行计算
public float calculate(float a , float b){
//如果没有对操作策略进行限定,那么系统默认使用加法操作策略
if(null == operation){
this.operation = new Addtion();
}
return this.operation.parameter(a,b);
}
public void setOperation(Operation operation){
this.operation = operation;
}
//测试
public static void main(String[] args){
//当没有对操作策略进行定义时,默认使用加法操作策略
OperationContext operationContext = new OperationContext();
System.out.println(operationContext.calculate(3,5));
//定义操作策略为乘法策略
operationContext.setOperation(new Multiplication());
System.out.println(operationContext.calculate(3,5));
}
}
运行上述伪代码,我们可以发现采用不同的策略,返回的结果就不同
8.0
15.0
使用该策略的优点是我们可以在不同的策略中进行切换,但是这增加了客户端跟不同策略的耦合程度。我们可以考虑使用配置文件来指定具体的操作方法。
门面模式
门面模式理解起来很简单,其实就是将一组复杂的类封装到一个简单的外部接口中。
比如说,我们实现某一个功能需要创建不同的类然后分别调用各个类的方法,为了简化该功能的调用,我们可以把创建以及调用等步骤封装到一个方法中,下次实现该功能,只需要调用封装好的这个方法即可。
伪代码如下:
原来的方式调用:
// 依次创建三个部门实例
Payment pay = new PaymentImpl();
Cook cook = new CookImpl();
Waiter waiter = new WaiterImpl();
// 依次调用三个部门实例的方法来实现用餐功能
String food = pay.pay();
food = cook.cook(food);
waiter.serve(food);
使用门面模式封装
public class Facade {
// 定义被Facade封装的三个部门
Payment pay;
Cook cook;
Waiter waiter;
// 构造器
public Facade() {
this.pay = new PaymentImpl();
this.cook = new CookImpl();
this.waiter = new WaiterImpl();
}
publicvoid serveFood() {
// 依次调用三个部门的方法,封装成一个serveFood()方法
String food = pay.pay();
food = cook.cook(food);
waiter.serve(food);
}
}
调用封装后的方法
Facade f = new Facade();
f.serveFood();
桥接模式
由于实际的需要,某个类具有两个以上的维度变化,如果只是使用继承将无法实现这种需要,或者使得设计变得相当臃肿。而桥接模式是把变化的部分抽象出来,使变化部分与主类分离开来,从而将多个的变化彻底分离,从而将多个变化彻底分离。最后提供一个管理类来组合不同维度上的变化。
创建Peppery
接口,定义一个风味接口
/**
* peppery风味接口
* Created by lany on 2018/9/10.
*/
public interface Peppery {
String style();
}
创建PlainStyle
类,实现风味接口,该风味为不辣
/**
*
* 实现不辣风格的口味
* Created by lany on 2018/9/10.
*/
public class PlainStyle implements Peppery{
@Override
public String style() {
return "重口味者慎入,养生党看过来.";
}
}
创建PepperyStyle
类,实现风味接口,该风味为超级辣
/**
* 实现超级辣口味的风格
* Created by lany on 2018/9/10.
*/
public class PepperyStyle implements Peppery{
@Override
public String style() {
return "味道非常辣,非重口味者勿入!";
}
}
创建AbstractNoodle
抽象类,该类为面条的口味的桥梁
/**
*
* 口味的桥梁
* Created by lany on 2018/9/10.
*/
public abstract class AbstractNoodle {
//组合一个peppery变量,用于将该维度的变化独立出来
protected Peppery style;
//每份noddle必须组合一个peppery对象
public AbstractNoodle(Peppery style){
this.style = style;
}
public abstract void eat();
}
创建PorkyNoodle
类,该类定义为猪肉面的口味介绍
public class PorkyNoodle extends AbstractNoodle{
public PorkyNoodle(Peppery style) {
super(style);
}
@Override
public void eat() {
System.out.println("这是一份很油腻的猪肉面条"
+super.style.style());
}
}
创建BeefNoodle
类,该类定义为牛肉面的口味介绍
public class BeefNoodle extends AbstractNoodle {
public BeefNoodle(Peppery style) {
super(style);
}
@Override
public void eat() {
System.out.println("这是一份鲜美的牛肉面"+super.style.style());
}
}
测试
public class Test {
public static void main(String[] args){
//一份超辣的美味牛肉面
AbstractNoodle abstractNoodle = new BeefNoodle(new PepperyStyle()) ;
abstractNoodle.eat();
}
}
其实在Java的dao层也是使用的该桥接模式。
观察者模式
观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。
在观察者模式中,被观察的对象常常也被称为目标或主题(Subject),依赖的对象被称为观察者(Observer)。
观察者:观察者也是一个接口,该接口规定了具体观察者用来更新数据的方法,伪代码如下:
创建Observer
接口,该接口用来规定了具体观察者用来更新的方法
**
* 观察者接口,该接口用来规定了具体观察者用来更新的方法
* Created by lany on 2018/9/10.
*/
public interface Observer {
/**
* 定义用来规定具体观察着用来更新的方法
* @param o
* @param arg
*/
void update(Observable o,Object arg);
}
具体观察者:具体观察者是实现了观察者接口的一个类。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,让自己成为它的观察者,或者让这个具体主题将自己从具体主题的集合中删除,使自己不在时它的观察者。伪代码如下:
创建NameObserver
类,该类为观察者接口的实现类
/**
* 具体观察者实现了观察者接口的一个类.具体观察者包含有可以存放具体主题引用的主题接口变量
* Created by lany on 2018/9/10.
*/
public class NameObserver implements Observer{
@Override
public void update(Observable o, Object arg) {
if(arg instanceof String){
String name = (String)arg;
JFrame f = new JFrame("观察者");
JLabel l = new JLabel("名称改变为:"+ name);
f.add(l);
f.pack();
f.setVisible(true);
System.out.println("观察者名称:"+o+"物品名称已经改变为:"+name);
}
}
}
主题:主题是一个接口,该接口规定了具体主题需要实现的方法,比如添加、删除观察者以及通知观察者更新数据的方法.伪代码如下:
创建Observable
抽象类
/**
* 目标或者主题
* 主题是一个抽象类,该接口规定了需要实现的方法,比如添加,删除观察者以及通知观察者更新数据的方法
* Created by lany on 2018/9/10.
*/
public abstract class Observable {
List<Observer> observers = new ArrayList<Observer>();
/**
* 注册观察者
* @param observer
*/
public void registObserver(Observer observer){
observers.add(observer);
}
/**
* 注销观察者
* @param observer
*/
public void removeObserver(Observer observer){
// observer.
}
/**
* 通知该主题上注册的所有观察者
* @param value
*/
public void notifyObservers(Object value){
for(Observer observer :observers){
observer.update(this,value);
}
}
}
具体主题:具体主题是一个实现主题接口的类,该类包含了会经常发生变化的数据。而且还有一个集合,该集合存放的是观察者的引用。伪代码如下:
创建Product
类,该类为被观察者类
/**
* 具体被观察者类
* Created by lany on 2018/9/10.
*/
public class Product extends Observable{
private String name;
private double price;
public Product(){}
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyObservers(name);
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
notifyObservers(price);
}
}
测试
public class Test {
public static void main(String[] args){
Product product = new Product("电视机",2999.99);
NameObserver no = new NameObserver();
product.registObserver(no);
product.setName("电脑");
System.out.println(product.getName());
}
}
通过上述伪代码,我们可以实现当产品的名称被改变之后,把改变的信息通知到所有观察者。