Java设计模式-装饰模式(Decorate)
目录
- 为什么要使用装饰模式
- 什么是装饰模式
- JavaSE装饰模式的应用
- Struts2装饰模式的应用
1、装饰模式是为了解决类功能扩展,而不增加类与子类的耦合性。
2、不要让自己停留在审计OWASP TOP10漏洞的阶段,赶紧突破。
一、为什么要使用装饰模式
1.1 使用原生的HttpServlet,实现对所有HTTP请求的参数过滤如何做?
我们知道Servlet可以在doGet、doPost中方法中接收HTTP的参数,如果要过滤参数,那么肯定要在doGet与doPost中处理,在每个doGet与doPost都做修改,会带来哪些问题?
- 重复代码过多
- 代码扩展性差
如果要修改需求,比如增加过滤的特殊字符,或者修改过滤的逻辑,都会引起所有过滤代码的修改,所以要用装饰模式解决问题(要彻底解决问题需要装饰模式+Filter,本文不体现Filter)。
1.2 实现过滤HTTP参数的功能
该项目复用【JSPServlet】项目代码,代码如下(只展示了核心代码)
SafeLogin.java :登录业务
MyHttpServletRequest:扩展的HttpServletRequestWrapper装饰类
web.xml :定义新增的SafeLogin类
public class SafeLogin extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 使用自定义的HttpServletRequest的装饰类,里面定义了参数的过滤方法
MyHttpServletRequest safeRequest = new MyHttpServletRequest(request);
// 这里调用的getParameter,是MyHttpServletRequest的getParameter方法,定义了过滤参数的方法
String userName = safeRequest.getParameter("username");
String password = safeRequest.getParameter("password");
System.out.println("过滤后 username:" + userName);
System.out.println("过滤后 password:" + password);
}
}
public class MyHttpServletRequest extends HttpServletRequestWrapper {
/**
* Constructs a request object wrapping the given request.
*
* @param request
* @throws IllegalArgumentException if the request is null
*/
public MyHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
// 调用父类的getParameter方法,也就是原生的HttpServletRequest
String value = super.getParameter(name);
System.out.println("过滤前 " + name + ":" + value);
value = value.replace("'","");
return value;
}
}
<servlet>
<servlet-name>safeLogin</servlet-name>
<servlet-class>org.example.SafeLogin</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>safeLogin</servlet-name>
<url-pattern>/safeLogin</url-pattern>
</servlet-mapping>
可以发现在SafeLogin中,使用了自定义的MyHttpServletRequest装饰类,该类继承了HttpServletRequestWrapper,所以,想要扩展HttpServletRequest,就需要继承HttpServletRequestWrapper类,有这样的好处:
- 对原始类HttpServletRequest没有任何侵入性,不必对原始类做任何修改
- 通过继承装饰类实现对原始类的装饰,扩展性好,也可以扩展多个装饰类,例如:过滤SQL字符、过滤XSS特殊字符等
对于初学者会有这样的疑问:
- 什么是装饰模式
- 为什么要继承HttpServletRequestWrapper,而不是HttpServletRequest
- HttpServletRequest与HttpServletRequestWrapper是什么关系
对于前两个问题我们在本文的后面小结解答,先看第三个问题
下面是关于HttpServletRequest的类UML图
根据装饰模式的特点,将HttpServletRequest的装饰模式分解如下(这是装饰模式的关键点,现在看不明白没关系,下面文章会详细讲解)
- 抽象组件(Component) :定义装饰方法的规范 – ServletRequest类
- 被装饰者(ConcreteComponent) :Component的具体实现,也就是我们要装饰的具体对象 – HttpServletRequest接口
- 装饰者组件(Decorator) :持有组件(Component)对象的实例引用,该类的职责就是为了装饰具体组件对象,定义的规范 – ServletRequestWrapper类
- 具体装饰(ConcreteDecorator) :负责给构件对象装饰附加的功能。-- HttpServletRequestWrapper、MyServletRequest类
了解完后,HttpServletRequest与HttpServletRequestWrapper是什么关系的问题迎刃而解,HttpServletRequestWrapper是HttpServletRequest的装饰类,通过装饰类扩展HttpServletRequest的功能。
为什么要继承HttpServletRequestWrapper,而不是HttpServletRequest?
一定要继承HttpServletRequestWrapper吗?其实不是,通过类的UML图可以看出,继承HttpServletRequestWrapper和继承ServletRequestWrapper是一样的,两者都是HttpServletRequest的装饰类。
最后一个问题,什么是装饰模式?什么情况下使用装饰模式?
- 你不希望修改原类,但想动态扩展原类功能,也可动态撤销
- 使用继承扩展对象行为不可行,继承是扩展对象功能最直接的方式,但代码耦合太强,而且最后不用继承层次太深
- 被final限制的类
二、什么是装饰模式
首先说一个开发场景
1月15日:产品经理找到你说:开发一个饮料售卖平台,前期只卖水
2月15日:产品经理又找你,说想卖可乐、芬达
3月1日:又找你,想可乐加冰
4月1日:又找你,想可乐加柠檬
可以看出产品的需求是多变的,各类设计模式的出现,解决的就是产品需求多变的问题而导致的代码重用性、耦合性、可维护性问题
比如第一次需求,我们只需要声明一个Water类就能完成需求
/**
* 可乐
*/
public class Water {
// 可乐的价格
final int COLA_PRICE = 5;
public int price() {
System.out.println("可乐需要 " + COLA_PRICE + " 元");
return COLA_PRICE;
}
}
而后面的可乐、芬达、加冰、加柠檬的需求,可以在定义4个类,分别满足需求,但需求是多变的,假如某天可乐中要加100种调料,难道要写100个类?每个类中都写price方法?如果客户想添加3中调料呢?那可能会想那在类中定义3个调料的价格计算就行了,但你要知道,用户加的调料是不同的,难道要写100个调料的排列组合?这简直不可能,因为会造成2个问题:
- 每个类都有计算可乐+调料的方法,造成重复代码多
- 如果增加/删除一种调料、修改调料价格,那么所有涉及到的类都要改
所以就要想办法解决上面的问题,如何更好的给可乐增加调料?
直接看代码吧,代码结构如下
Water.java
package org.decorate.drinks;
/**
* 最终要装饰的类,装饰为不同的饮料
*/
public interface Water {
/**
* 饮料的价格
* @return
*/
public int price();
}
drinks.Cola.java
package org.decorate.drinks.drinks;
import org.decorate.drinks.Water;
/**
* 可乐
*/
public class Cola implements Water {
// 可乐的价格
final int COLA_PRICE = 5;
public int price() {
System.out.println("可乐需要 " + COLA_PRICE + " 元");
return COLA_PRICE;
}
}
drinks.Fanta.java
package org.decorate.drinks.drinks;
import org.decorate.drinks.Water;
/**
* 芬达饮料
*/
public class Fanta implements Water {
// 芬达的价格
final int FANTA_PRICE = 3;
public int price() {
System.out.println("芬达需要 " + FANTA_PRICE + " 元");
return FANTA_PRICE;
}
}
decorate.LceDecorate.java
package org.decorate.drinks.decorate;
import org.decorate.drinks.Water;
/**
* 加冰
*/
public class IceDecorate implements Water {
private Water water = null;
private int ICE_PRICE = 1;
public IceDecorate(Water water) {
this.water = water;
}
public int price() {
// 水的价格 + 冰的价格
System.out.println("加冰需要 " + ICE_PRICE + " 元");
return this.water.price() + ICE_PRICE;
}
}
decorate.LemonDecorate.java
package org.decorate.drinks.decorate;
import org.decorate.drinks.Water;
/**
* 加柠檬
*/
public class LemonDecorate implements Water {
private Water water = null;
private int LEMON_PRICE = 2;
public LemonDecorate(Water water) {
this.water = water;
}
public int price() {
System.out.println("加柠檬需要 " + LEMON_PRICE + " 元");
// 水的价格 + 柠檬的价格
return this.water.price() + LEMON_PRICE;
}
}
最终如何调用
package org.decorate.drinks;
import org.decorate.drinks.decorate.IceDecorate;
import org.decorate.drinks.decorate.LemonDecorate;
import org.decorate.drinks.drinks.Cola;
public class Customer {
public static void main(String[] args) {
// 客户要可乐加冰加柠檬
Water water = new Cola();
water = new IceDecorate(water);
water = new LemonDecorate(water);
int price = water.price();
System.out.println("顾客需要支付 " + price + "元");
}
}
在Customer类中选择饮料时,可以发现无论我如何选择调料,都不需要改其它类,只需要修改调用的代码即可,非常方便,哪怕后来多了其它饮料的种类,想加冰加柠檬都是可以直接调用的,这就是装饰模式的好处
我们抽象下装饰模式的基本特性
- 抽象构建Component(接口or抽象类 – Water)
- 抽象构建的实现类(主要是对这个类进行装饰 – Cola、Fanta)
- 具体的装饰类(你要装饰的代码类 – IceDecorate、LemonDecorate ,属性里必然有一个private变量指向Component抽象构(Water))。
类的关系图如下
也可以将装饰类抽象为抽象方法,形成如下图
再次抽象下装饰模式的基本特性
- 抽象组件(Component) :定义装饰方法的规范 – Water类
- 被装饰者(ConcreteComponent) :Component的具体实现,也就是我们要装饰的具体对象 – Cola、Fanta类
- 装饰者组件(Decorator) :持有组件(Component)对象的实例引用,该类的职责就是为了装饰具体组件对象,定义的规范 – Decorator类
- 具体装饰(ConcreteDecorator) :负责给构件对象装饰附加的功能。-- IceDecorator、lemonDecorator类
看下具体代码,其中Water.java、Cola.java、Fantta.java类与前面一样,主要看下IceDecorator、lemonDecorator、Decorator类
Water.java
package org.decorate.drinks;
/**
* 最终要装饰的类,装饰为不同的饮料
*/
public interface Water {
/**
* 饮料的价格
* @return
*/
public int price();
}
drinks.Cola.java
package org.decorate.drinks.drinks;
import org.decorate.drinks.Water;
/**
* 可乐
*/
public class Cola implements Water {
// 可乐的价格
final int COLA_PRICE = 5;
public int price() {
System.out.println("可乐需要 " + COLA_PRICE + " 元");
return COLA_PRICE;
}
}
drinks.Fanta.java
package org.decorate.drinks.drinks;
import org.decorate.drinks.Water;
/**
* 芬达饮料
*/
public class Fanta implements Water {
// 芬达的价格
final int FANTA_PRICE = 3;
public int price() {
System.out.println("芬达需要 " + FANTA_PRICE + " 元");
return FANTA_PRICE;
}
}
Decorator.java
package org.decorate.drinks2.decorate;
import org.decorate.drinks2.Water;
/**
* 装饰抽象类
*/
public abstract class Decorate implements Water {
private Water water = null;
public Decorate(Water water){
this.water = water;
}
public int price() {
return this.water.price();
}
}
decorate.IceDecorator.java
package org.decorate.drinks2.decorate;
import org.decorate.drinks2.Water;
/**
* 加冰
*/
public class IceDecorate extends Decorate{
final private int ICE_PRICE = 1;
public IceDecorate(Water water) {
super(water);
}
public int price() {
// 水的价格 + 冰的价格
System.out.println("加冰需要 " + ICE_PRICE + " 元");
return super.price() + ICE_PRICE;
}
}
decorate.LemonDecorator.java
package org.decorate.drinks2.decorate;
import org.decorate.drinks2.Water;
/**
* 加柠檬
*/
public class LemonDecorate extends Decorate{
final private int LEMON_PRICE = 1;
public LemonDecorate(Water water) {
super(water);
}
public int price() {
// 水的价格 + 柠檬的价格
System.out.println("加柠檬需要 " + LEMON_PRICE + " 元");
return super.price() + LEMON_PRICE;
}
}
对比下两种实现方式,可以发现后面一种的类关系更加清晰,而且每个具体的装饰类中都减少了Water类的引用,减少了代码量,也减少了对象的生成。
三、JavaSE装饰模式的应用
JavaSE中对装饰模式的应用,最典型的就是IO流,例如下面代码
static final String FILE = "test.log";
Reader reader = new InputStreamReader(new FileInputStream(FILE));
在大学中学习Java时,只知道这句话是创建了InputStreamReader对象,构造方法传递了FileInputStream对象,这样就可以读取一个文件,但并不明白为什么这么做,也没有调试过JDK的源码,并不明白什么是设计模式,只知道如何去用。
到现在开始分析Java代码安全时,分析的越深入,发现很多设计模式如果不明白,就会限制自己。
这里想到了《凤凰项目一个IT运维的传奇故事》的一句话
创建约束理论的艾利高德拉特告诉我们,在瓶颈之外的任何地方做出的改进都是假象。
所以必须要解决瓶颈问题,也就是对设计模式的盲区,要不然自己会永远停留在审计OWASP TOP10的阶段,那将是职业生涯的灾难。
从前面的读文件代码,就是非常典型的装饰模式,首先先看三种不同的对文件读取的方式
package org.decorate;
import java.io.*;
public class JavaSEDecorate {
static final String FILE = "test.log";
public static void main(String[] args) throws IOException {
test1();
}
public static void test1() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(FILE));
String str;
while ((str = in.readLine()) != null) {
System.out.println(str);
}
in.close();
}
public static void test2() throws IOException{
InputStream in = new FileInputStream(FILE);
int tempbyte;
while ((tempbyte = in.read()) != -1) {
System.out.write(tempbyte);
}
in.close();
}
public static void test3() throws IOException {
char[] tempchars = new char[3];
int charread = 0;
Reader reader = new InputStreamReader(new FileInputStream(FILE));
// 读入多个字符到字符数组中,charread为一次读取字符数
while ((charread = reader.read(tempchars)) != -1) {
System.out.print(tempchars);
}
}
}
以上面的三种方式的一个为例
BufferedReader in = new BufferedReader(new FileReader(FILE));
回忆下前面关于装饰模式的四个关键点,但这个只用到了三个关键点的模式,对应到BufferedReader类中
- 抽象组件(Component) :定义装饰方法的规范(FileReader与BufferedReader的共同父类) – Reader类
- 被装饰者(ConcreteComponent) :Component的具体实现,也就是我们要装饰的具体对象 – FileReader类
- 具体装饰(ConcreteDecorator) :负责给构件对象装饰附加的功能,持有组件(Component)对象的实例引用Reader。-- BufferedReader类
可以看到非常符合装饰模式的应用
FileReader封装StreamDecoder类封装FileInputStream类,最终BufferedReader.readLine方法,调用的是StreamDecoder.read()方法,只不过BufferedReader.readLine方法进行了字符串的封装处理
为什么要这么嵌套呢?我们读文件时,比较自然的思路是逐行阅读,这是比较贴近人类的阅读方式,OK,我们看下FileReader类中有哪些方法,点一下下图标红按钮,可以看到父类继承的方法
可以看到只有read方法,并没有我们想的readLine方法,在看下BufferedReader类的方法
可以看到BufferedReader类中是有readLine方法,在FileReader类的基础上装饰
了readLine方法
附一张IO流的继承关系图
通过上图可以看到IO流有字节流与字符流两类,而如何将字节转为字符流?答案是InputStreamReader
和OutputStreamWriter
,其中用都是装饰模式的设计方法
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("text.txt"))));
四、Struts2装饰模式的应用
- StrutsRequestWrapper
- OgnlContext
- OgnlTypeConverterWrapper
- OgnlNullhandlerWrapper
首先StrutsRequestWrapper扩展的是HttpServletRequestWrapper类,和我们前面自定义的MyServletRequest是相同道理,不在赘述
OgnlContext类代码如下,扩展的是Map类,内部持有一个Map对象
public class OgnlContext extends Object implements Map
{
private static Map RESERVED_KEYS = new HashMap(11);
private Map _values = new HashMap(23);
private int _localReferenceCounter = 0;
private Map _localReferenceMap = null;
public OgnlContext(Map values)
{
super();
this._values = values;
}
public Map getValues()
{
return _values;
}
public void setValues(Map value) {
for(Iterator it = value.keySet().iterator(); t.hasNext();) {
Object k = it.next();
_values.put(k, value.get(k));
}
}
//省略了代码...
}
类的关系图如下
- 抽象组件(Component) :定义装饰方法的规范(FileReader与BufferedReader的共同父类) – Object类
- 被装饰者(ConcreteComponent) :Component的具体实现,也就是我们要装饰的具体对象 – Map接口
- 具体装饰(ConcreteDecorator) :负责给构件对象装饰附加的功能,持有组件(Component)对象的实例引用Map。-- OgnlContext类
OgnlTypeConverterWrapper类的解析如下
- 抽象组件(Component) :TypeConverter接口
- 被装饰者(ConcreteComponent) :DefaultTypeConverter类
- 具体装饰(ConcreteDecorator) :OgnlTypeConverterWrapper类、XWorkConverter(Struts中的装饰类)
public interface TypeConverter
{
public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType);
}
public class OgnlTypeConverterWrapper implements ognl.TypeConverter { //接口实现是ognl接口
private TypeConverter typeConverter;//实际传参是Struts自己的接口内
public OgnlTypeConverterWrapper(TypeConverter conv) {
if (conv == null) {
throw new IllegalArgumentException("Wrapped type converter cannot be null");
}
this.typeConverter = conv;
}
public Object convertValue(Map context, Object target, Member member, //他们有完全一样的方法
String propertyName, Object value, Class toType) {
return typeConverter.convertValue(context, target, member, propertyName, value, toType);
}
public TypeConverter getTarget() {
return typeConverter;
}
}
public class DefaultTypeConverter implements TypeConverter {
public Object convertValue(Map<String, Object> context, Object target, Member member,
String propertyName, Object value, Class toType) {
return convertValue(context, value, toType);
}
public TypeConverter getTypeConverter( Map<String, Object> context )
{
Object obj = context.get(TypeConverter.TYPE_CONVERTER_CONTEXT_KEY);
if (obj instanceof TypeConverter) {
return (TypeConverter) obj;
// for backwards-compatibility
} else if (obj instanceof ognl.TypeConverter) {
return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj);
}
return null;
}
public Object convertValue(Object value, Class toType) {
Object result = null;
if (value != null) {
/* If array -> array then convert components of array individually */
if (value.getClass().isArray() && toType.isArray()) {
Class componentType = toType.getComponentType();
........省略代码
public class XWorkConverter extends DefaultTypeConverter {
protected HashMap<Class, Map<String, Object>> mappings = new HashMap<Class, Map<String, Object>>(); // action
protected HashSet<Class> noMapping = new HashSet<Class>(); // action
protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>(); // non-action (eg. returned value)
protected HashSet<String> unknownMappings = new HashSet<String>(); // non-action (eg. returned value)
// 要装饰的对象,持有组件(Component)对象的实例引用-满足要求
private TypeConverter defaultTypeConverter;
private ObjectFactory objectFactory;
private FileManager fileManager;
private boolean reloadingConfigs;
protected XWorkConverter() {
}
@Inject
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
// note: this file is deprecated
loadConversionProperties("xwork-default-conversion.properties");
loadConversionProperties("xwork-conversion.properties");
}
@Inject //注入方法
public void setDefaultTypeConverter(XWorkBasicConverter conv) {
this.defaultTypeConverter = conv;
}
//省略许多方法和属性...
}
OgnlNullhandlerWrapper类的解析如下
- 抽象组件(Component) :NullHandler接口
- 被装饰者(ConcreteComponent) :ObjectNullHandler类
- 具体装饰(ConcreteDecorator) :OgnlNullHandlerWrapper类
public interface NullHandler
{
public Object nullMethodResult(Map context, Object target, String methodName, Object[] args);
public Object nullPropertyValue(Map context, Object target, Object property);
}
public class ObjectNullHandler implements NullHandler
{
/* NullHandler interface */
public Object nullMethodResult(Map context, Object target, String methodName, Object[] args)
{
return null;
}
public Object nullPropertyValue(Map context, Object target, Object property)
{
return null;
}
}
public class OgnlNullHandlerWrapper implements ognl.NullHandler {
// 持有的被装饰的对象
private NullHandler wrapped;
public OgnlNullHandlerWrapper(NullHandler target) {
this.wrapped = target;
}
public Object nullMethodResult(Map context, Object target,
String methodName, Object[] args) {
return wrapped.nullMethodResult(context, target, methodName, args);
}
public Object nullPropertyValue(Map context, Object target, Object property) {
return wrapped.nullPropertyValue(context, target, property);
}
}