设计模式简介
了解设计模式之前必须要知道设计模式作用,使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
设计模式的六大原则
开闭原则(Open Close Principle)
你扩展的时候尽量不要去对原来代码进行修改,为了使代码扩展性好,易于维护和升级。java的接口和抽象类可以帮助我们达到这样的效果。
里氏代换原则(Liskov Substitution Principle)
替代性是面向对象编程中的一个原则,表示在计算机程序中,如果S是T 的子类型,那么类型 T的对象可以用 S类型的对象替换(即,类型T的对象可以被替换为任何子类型S的对象),而不改变T的任何期望属性(正确性,执行任务等)。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
依赖倒转原则(Dependence Inversion Principle)
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
接口隔离原则(Interface Segregation Principle)
ISP将非常大的接口分解成更小和更具体的接口,以便客户端只需要知道他们感兴趣的方法。这种缩小的接口也称为角色接口。ISP旨在保持系统解耦,从而更容易重构,更改和重新部署。-
迪米特法则(最少知道原则)(Demeter Principle)
DP可以更精确地称为“功能/方法的Demeter定律”(LoD-F)。在这种情况下,对象A可以请求对象实例的服务(调用方法)B,但是对象A不应该“通过”对象B访问另一个对象C,以请求其服务。这样做意味着该对象A隐含地需要更多的对象B内部结构的知识。就是说在通过对象复用代码的时候,你只是使用它的功能,不能依赖它的其他东西,因为这个依赖不稳定。
合成复用原则(Composite Reuse Principle)
类应该通过组合实现多态性行为和代码重用的原则(通过包含实现所需功能的其他类的实例),而不是从基础或父级继承类。
设计模式的六大原则的思考
开闭原则(Open Close Principle)
开闭原则在扩展业务的时候尽量不要对原来代码进行修改,如果它代码是可用的,只是缺少或者需要扩展我们可以再写一个方法对其进行扩充,但是这个方法必须是可以复用的。java的接口和抽象类可以帮助我们达到这样的效果,接口和抽象类的方法相当于概化的“零部件”,不改变原来零部件只是再造一个零部件去和原来零部件协同工作。
回头再看这个原则受益匪浅,前段时间在支付回调里面增加了各种支付成功奖励,把支付回调越改越复杂,然后新增的业务异常也影响了原来正常的支付,紧急修复之后怕还有bug直接catch住了,但是设计还是有问题的,有时候为了新增业务回去稍微修改原来代码,这样就很不对,原来没问题为啥要修改谁也不能保证你的修改会绝对不会影响原来业务,所以按照这个思想来,我们再增加一个servise方法给api调用,这样我们以后不想奖励了直接去掉这个servise方法。
里氏代换原则(Liskov Substitution Principle)
里氏代换原则感觉没多大缺陷,说了只有在替换前和替换后没差别时才替换,但是也不能为了替换而替换。
依赖倒转原则(Dependence Inversion Principle)和合成复用原则(Composite Reuse Principle)
这两个原则是互补的,想要代码复用,你尽量不要在实现某个方法的时候去继承某个方法,复用代码你可以使用对象来调用方法。这样能降低类之间、模块之间的耦合。但是过多的使用对象会给回收带来负担,影响代码执行效率。
接口隔离原则(Interface Segregation Principle)
接口隔离原则说让你尽量把接口变小,接口方法不能太大就像把所有零件都焊接在一起,不够灵活,对今后维护带来问题。但接口过多功能太细化不便于管理,使编程眼花缭乱,要维持一个度(这个度很难)。
迪米特法则(最少知道原则)(Demeter Principle)
这个原则本来也是java面向对象的三大特性之封装,你只能使用它允许你使用的方法,不能获得类成员去使用完成功能,不能通过了解它的内部结构而达到其他目的,因为内部结构可能会改变。
单例模式
单例模式意思就是类对象只产生一个,整个应用只有一个实例,目的是有些应用是要求只能有一个实例或者避免创建多个对象浪费资源。
单例模式实现:构造函数私有化,在本类进行创建对象,定义一个公共方法进行访问类对象。
现在实现比较方便,可以用spring管理对象,控制单例。
工厂模式
工厂模式分为三种:
普通工厂
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:
public interface Sender {
public void Send();
}
其次,创建实现类:
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
最后,建工厂类:
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
我们来测试下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
输出:this is sms sender!
多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
将上面的代码做下修改,改动下SendFactory类就行,如下:
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
测试类如下:
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
输出:this is mailsender!
静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
适配器模式
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。就是通过继承、实现、使用实例来柔和到一个类中,复用代码,并且不改变原来的代码,完成期望的功能。、
就比如有一个统一处理方法,方法参数类型是某个接口,方法内部会调用这个接口的某些方法,但是有一些类是继承了某个抽象父类并没有实现该接口所以不能使用该统一方法,那么对象适配器就可以排上用场了,可以创建个适配器实现这个接口,有属性是抽象类型的,内部调用对象实现方法。这样做法是相当明智的,因为不改变任何东西实现了目的。
类的适配器模式
核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口时Targetable,通过Adapter类,将Source的功能扩展到Targetable里,看代码:
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
public interface Targetable {
/* 与原类中的方法相同 */
public void method1();
/* 新类的方法 */
public void method2();
}
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
Adapter类继承Source类,实现Targetable接口,下面是测试类:
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
输出:
this is original method!
this is the targetable method!
这样Targetable接口的实现类就具有了Source类的功能。
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
只需要修改Adapter类的源码即可:
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();
}
}
测试类:
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
输出与第一种一样,只是适配的方法不同而已。
接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。AbstractList就是接口List的适配器。
这个很好理解,在实际开发中,我们也常会遇到这种接口中定义了太多的方法,以致于有时我们在一些实现类中并不是都需要。看代码:
public interface Sourceable {
public void method1();
public void method2();
}
抽象类Wrapper2:
public abstract class Wrapper2 implements Sourceable{
public void method1(){}
public void method2(){}
}
public class SourceSub1 extends Wrapper2 {
public void method1(){
System.out.println("the sourceable interface's first Sub1!");
}
}
public class SourceSub2 extends Wrapper2 {
public void method2(){
System.out.println("the sourceable interface's second Sub2!");
}
}
public class WrapperTest {
public static void main(String[] args) {
Sourceable source1 = new SourceSub1();
Sourceable source2 = new SourceSub2();
source1.method1();
source1.method2();
source2.method1();
source2.method2();
}
}
测试输出:
the sourceable interface’s first Sub1!
the sourceable interface’s second Sub2!
装饰模式
顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,装饰对象持有被装饰对象的实例。有点像对象适配器模式,但是装饰器要求要求装饰对象和被装饰对象实现同一个接口。例如:FilterInputStream和FilterOutputStream,还有代理。
Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能,代码如下:
public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
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!");
}
}
测试类:
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
输出:
before decorator!
the original method!
after decorator!
外观模式
外观模式顾明思议就是外观,它自己没有实体是靠别人组装而成。外观模式是为了解决类与类之间的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
我们以一个计算机的启动过程为例,我们先看下实现类:
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
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!");
}
}
User类如下:
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
输出:
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!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这,就是外观模式!
模版方法模式
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:
public abstract class AbstractCalculator {
/*主方法,实现对本类其它方法的调用*/
public final int calculate(String exp,String opt){
int array[] = split(exp,opt);
return calculate(array[0],array[1]);
}
/*被子类重写的方法*/
abstract public int calculate(int num1,int num2);
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
public class Plus extends AbstractCalculator {
@Override
public int calculate(int num1,int num2) {
return num1 + num2;
}
}
测试类:
public class StrategyTest {
public static void main(String[] args) {
String exp = "8+8";
AbstractCalculator cal = new Plus();
int result = cal.calculate(exp, "\\+");
System.out.println(result);
}
}
我跟踪下这个小程序的执行过程:首先将exp和"\+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
命令模式
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
public interface Command {
public void exe();
}
public class MyCommand implements Command {
private Receiver receiver;
public MyCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void exe() {
receiver.action();
}
}
public class Receiver {
public void action(){
System.out.println("command received!");
}
}
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action(){
command.exe();
}
}
public class Test {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command cmd = new MyCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.action();
}
}
输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
观察者模式
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该 记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
public interface Observer {
public void update();
}
两个实现类:
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
Subject接口及实现类:
public interface Subject {
/*增加观察者*/
public void add(Observer observer);
/*删除观察者*/
public void del(Observer observer);
/*通知所有的观察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
测试类:
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
输出:
update self!
observer1 has received!
observer2 has received!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
状态模式
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。
State类是个状态类,Context类可以实现切换,我们来看看代码:
package com.xtfggef.dp.state;
/**
* 状态类的核心类
* 2012-12-1
* @author erqing
*
*/
public class State {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public void method1(){
System.out.println("execute the first opt!");
}
public void method2(){
System.out.println("execute the second opt!");
}
}
package com.xtfggef.dp.state;
/**
* 状态模式的切换类 2012-12-1
* @author erqing
*
*/
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void method() {
if (state.getValue().equals("state1")) {
state.method1();
} else if (state.getValue().equals("state2")) {
state.method2();
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
State state = new State();
Context context = new Context(state);
//设置第一种状态
state.setValue("state1");
context.method();
//设置第二种状态
state.setValue("state2");
context.method();
}
}
输出:
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
策略模式
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。
ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
public interface ICalculator {
public int calculate(String exp);
}
辅助类:
public abstract class AbstractCalculator {
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
三个实现类:
public class Plus extends AbstractCalculator implements ICalculator {
@Override
public int calculate(String exp) {
int arrayInt[] = split(exp,"\\+");
return arrayInt[0]+arrayInt[1];
}
}
public class Minus extends AbstractCalculator implements ICalculator {
@Override
public int calculate(String exp) {
int arrayInt[] = split(exp,"-");
return arrayInt[0]-arrayInt[1];
}
}
public class Multiply extends AbstractCalculator implements ICalculator {
@Override
public int calculate(String exp) {
int arrayInt[] = split(exp,"\\*");
return arrayInt[0]*arrayInt[1];
}
}
简单的测试类:
public class StrategyTest {
public static void main(String[] args) {
String exp = "2+8";
ICalculator cal = new Plus();
int result = cal.calculate(exp);
System.out.println(result);
}
}
输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
下面例子类似。
import java.util.Arrays;
public class Apply {
public static void process(Processor p, Object s) {
System.out.println(p.process(s));
}
public static String s = "cdsds Asdsd asdasd";
public static void main(String[] args) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
}
interface Processor {
Object process(Object input);
}
class Upcase implements Processor {
@Override
public String process(Object input) {
return ((String) input).toUpperCase();
}
}
class Downcase implements Processor {
@Override
public String process(Object input) {
return ((String) input).toLowerCase();
}
}
class Splitter implements Processor {
@Override
public String process(Object input) {
return Arrays.toString(((String) input).split(" "));
}
}
实际使用操作过程。
- 每个“Processor”都要创建一个接口
- 不同的处理器又要实现它,如果策略多的要新建很多文件
- 实际使用要维护一个枚举,用来对应用户请求类型type和处理beaName,同是需要注入一个map key-beanName,value-类型为处理器类型的bean,使用的时候就只需要根据用户请求类型type自动去找对应beanName然后从map中取出来再执行统一方法。
感觉下来就是能够根用户不同的请求进行统一处理,依赖于同一接口实现的不同处理类,这样能使代码看上去特别清爽,业务代码和具体的“处理器”实现解耦,维护其他也简单明了不用去更改业务代码只需要增加枚举和处理器。
但是我是觉得操作步骤比较繁琐,想要实现这个效果,可以使用方法反射,枚举需要维护一个属性去知道是哪个处理器接口,beanName变成methodName,这样只需要创建一个处理器实现类,维护的话只需要增加枚举和新增方法就行。
XXActionType.handlerClass.getMethod(XXActionType.getHandlerMehodAccordingType(request.getType()),... parameterTypes).invoke(XXHandlerbean,... args);
个人实现方式,如有不对欢迎指出