1.用java角度谈一切皆类型
面向对象范式的第一原则是柏拉图法则,它是对象技术的观念范式和心理范式的根源,对象技术是通过颠倒的理念世界而模拟唯物的真实世界。
★柏拉图法则:类的世界存在,对象世界由类创建而来 提出的依据:
(1)对象(object)是一个具有浓郁的哲学味道的术语,原意是指用一种或多种(人的)感官,尤其指用视觉或触觉可以感觉到的东西、物体或物品。真实世界就是由对象/客体组成的。
(2)人们看见这条黄狗、那条哈巴狗,这时,人类发挥其抽象(v. abstract)能力,从一个个的具体对象中提炼出类别/类型性的概念„狗‟或„Dog‟。进一步的演化则令人困惑——人类抽象出一些没有直接的物理对应物的概念。例如,几何中的“圆”,现实生活中没有一个对象能完全满足圆在数学上的完备性,即使小心翼翼地使用圆规。
(3)软件的问题域是真实世界的一部分。柏拉图的理念论,正好适用于软件的解域:一切皆概念/类型!
2.用面向对象方面说明柏拉图原则的意义 二.Parnas原则(接口与实现分离)
1.从c语言角度说明接口与实现分离(视角方面) 2.Java角度来谈Parnas面向对象领域的推广 3.什么是封装
三.依赖抽象原则
1.抽象依赖,开放封闭原则,针对接口编程什么区别,解释、 2.Java代码说明依赖注入三种方式 依赖注入模式
依赖注入(Dependency Injection) 是一个非常简单的概念。如例程1-5所示,Client依赖于抽象类型(甚至是具体类) IServer,但是Client的类体中不创建IServer的(子类)对象,它不关心如何初始化IServer变量,而是提供public的构造器Client(IServer)或设置方法setIServer (IServer)等,坐等外界将初始化后的IServer对象(的引用)传递进来。
依赖注入模式的基本思想是客户类Client不用自己来初始化它所依赖的成员变量IServer,使用一个的对象创建IServer的适当的实现类并将它赋值给Client的成员变量。
依赖注入的意义,在于保证Client仅仅与(通常是接口或抽象类)IServer耦合,而不与IServer的子类型耦合,这样的程序符合OCP或依赖于抽象类型原则。
只要知道方法参数的意义是什么,依赖注入不过是简单的应用。
1. 例程 1-5 2. package creational; 3. import java.lang.reflect.*; 4. import tool.God; 5. public class Client{ 6. private IServer s; /*依赖注入*/ 7. public Client(IServer s){ this.s = s; } 8. public void setS(IServer s){ this.s = s; } 9. public static void test(){ //使用工具God 10. IServer s = (IServer) God.create(\"1-4\"); 11. s.m(); }}
2.1注入的方式
站在Client的角度,Client接受注入有3种的方式。 1.构造器注入
构造器注入(Constructor Injection)时,客户类Client提供了public的构造器Client (IServer s),等待外界创建IServer的(实现类的)对象后将其引用传递进来/注入。
publicClient(IServer s){ this.s=s; } 2.Setter注入
Setter注入(Setter Injection)时,客户类Client提供setIServer (IServers),等待外界创建IServer的(实现类的)对象后将其引用传递进来/注入。
publicvoid setIServer (IServer s){ this.s=s; } 构造器注入与Setter注入是两种常用的方式。构造器注入的优点是创建Client对象时,确保IServer对象被初始化;而采用Setter注入,则创建Client对象后,必须在适当的时候调用setIServer()方法,这就使得程序员可以在灵活的时间(例如希望惰性初始化)要求外界完成注入。
3.接口注入
接口注入相当于将Setter注入的setIServer (IServer s) 方法封装到一个专用的接口如InjectIServer中,而Client实现InjectIServer并给出如下的方法体。
@Overridepublic void setIServer (IServer s){ this.s=s; }
接口注入针对的场景是,有大量Client、Client1等都需要依赖于IServer。 public interface InjectIServer{
publicvoid setIServer (IServer s);}
代码中使用了[4.1虚域模式]。然而这一方式并不一定被各种依赖注入容器所支持。 3.使能工具(说明依赖注入是抽象依赖原则的使能工具,根据依赖注入实现抽象依赖?) 依赖注入容器/框架
依赖注入模式中,Client等待外界或一个的对象——称为注射器——创建IServer的适当的实现类并将它赋值给Client的成员变量。
3.1显然,这个注射器不应该是系统中的某个类:
1. /* 2. class Injection{//注射器不应该是这样的 3. public static void test(){ 4. IServer s = new Server(); 5. Client c = new Client(); 6. c.setIServer(s);//注入 7. c.show(); } }*/
这里Injection依赖于Server,如果它和Client在一个包中,就使得早期的目标——Client仅与IServer耦合破坏殆尽,还不如Client直接与具体类Server耦合。
3.2由于我们拥有工具类tool.God,在任何应用程序App中,可以用God作为注射器。因而,依赖注入意味着使用反射机制创建对象。代码中,IServer对象的创建使用了tool.God的静态方法create(), create()不过是一个使用反射+属性配置文件(.properties文件)创建对象的静态工厂。
1. package creational.di; 2. import tool.God 3. public class App{ //Injection 4. public static void test(){
5. 6. 7. 8. IServer s = (IServer) God.create(\"1-6\"); //1-6 = creational.di.Server Client c = new Client();
c.setIServer(s);//注入 c.show(); } }
★单就学习设计模式而言,工具类God已经足够。站在Client的角度,依赖注入模式对待外界创建并传入对象。
请注意,至少到目前为止,我们没有说任何特别的术语——依赖倒置原则DIP和控制反转IoC,而此时,我会将依赖注入(Dependency Injection)作为一种设计模式。(也就是说,依赖注入与IoC不是同一个概念)
3.3但是,比工具类God更为强大的依赖注入容器,如Spring、PicoContainer等,它们认为使用/依赖关系是面向对象编程的最基本的程序结构,各种各样的使用关系如Client与IServer、C与S等等广泛存在,作为一个依赖注入的工具或框架,希望程序员不再编写如下代码: Client c = new Client();
IServer s = (IServer) God.create(\"1-6\");
c.setIServer(s);//注入 c.show();
设想一下,如果Client依赖很多的类似IServer的服务类型,省略掉上述阴影标识的代码,将能够节省程序员大量的时间。各种用于依赖注入的专用框架被开发出来如Spring等,它们被称为依赖注入或控制反转容器(DI/IoC Container)。
四.好莱坞原则、单向依赖
1.介绍好莱坞法则。单向依赖原则
2.Java演示轮询,轮询vs通知的优缺点
在分层架构中,上层模块Client调用了下层模块Server的copy()方法,上层并不清楚复制的进度而只有下层的Server才知道。上层获得进度数据的方式:轮询和通知。通知的代码见回调与Java8的λ表达式。
2.1轮询。下层模块Server0将进度数据保存在一个成员变量x中,并提供getX()。Client通过轮询访问该数据。
轮询方式下,一个线程中Server0努力的复制,主线程则在while(true)中随时随地的查询数据,直到复制进度数据为100才结束。
1. package principle.callback.lower; 2. public class Server0{
3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
private int x;//复制工作的进度 public int getX(){ return x; } public void copy() { while(x<100){
try{ Thread.sleep(10);
x++; }catch(InterruptedException e){} }}} package principle.callback;
import principle.callback.lower.Server0; public class Client0{
private Server0 server =new Server0(); public void call() { server.copy(); }
public static void test() throws InterruptedException{
15. Client0 c =new Client0(); 16. Runnable r = ()->c.call(); 17. new Thread(r).start(); 18. System.out.print(\"进度:\"); 19. int x2 = 0;
20. while(true){ int x1 = c.server.getX();///10 21. Thread.sleep((int)(Math.random()*100)); 22. if(x2 >=100){ System.out.println(); 23. break; }
24. if (x2 != x1) {System.out.print(x1+\"% \"); 25. x2 = x1; }}}}
下面是两次测试的结果。
进度:3% 5% 9% 16% 21%23% 29% 31% 38% 48% 57% 63% 70% 76% 82% 85% 87% 88% 90% 91% 97% 100%
进度:1% 6% 12% 16% 24%31% 39% 47% 49% % 61% % 67% 77% 83% 90% 100% 2.2轮询与通知的优缺点:
轮询为系统服务资源分配提供了非竞争的访问控制机制,其控制实现过程简洁可靠,可以和有效避免接入对象间的竞争冲突,特别在高负载情况下能够获得较优的共享资源利用率。轮询的;轮询调度的缺点就是没有优先级,只按照先设计好的轮询顺序进行调度,再就是占用大量服务器系统资源,编程难度高,实时性差。
通知为系统资源分配提供了优先级,可以按照事件的优先级进行调度。实时性好,比适适合保护方面的通信方式;缺点就是其控制过程比较复杂,接入对象存在竞争冲突。
(补)轮询方式的优点:上层模块能够随时随地的了解所需的数据(不会被下级欺瞒),依赖关系和方法调用关系都很明确;下层模块代码简洁。
缺点:上级时刻盯着下级干活,比较讨嫌;获得数据时执行太多的调用(特别是在网络编程如远程方法调用时,这是不能够容忍的);
回调例子:它由上层模块Client、TestCallback和下层被调用者Server和公共模块IClient组成。Client的callback(int) 方法被称为回调。而IClient定义的抽象方法callback(int) 被称为回调接口,大多数情况下,回调接口也简称回调。
package API.event.lower;
/*通常在公共模块中设计一个抽象类或接口如IClient,定义回调的契约/
规范。公共模块通常有自己的包。
public interface IClient{
/*回调接口,参数为底层将上传的数据例如 copy的进度。*/ public void callback(int i);} package API.event.lower;
/*下层Server知道拷贝的进度。 copy()在适当的时机调用回调来通知上层。 public class Server{
private IClient whoCallMe;//必须获得一个IClient 的引用,由构造器的参数提供
1. 2. 3. 4. 5. 6. 7.
8. 9. 10. 11. 12.
public Server(IClient listener) {// whoCallMe = listener; } public void copy() {
for(int i=0;i<100;i++){//在适当的时机调用回调
if (i%10 == 0) { whoCallMe.callback(i/10); }}
13. System.out.println(\"copy() over\"); }}
上层模块代码如下:
package API.event;//意味上层模块所在的包 import API.event.lower.Server;//使用 import API.event.lower.IClient;//继承
/*上层模块Client,是公共模块/下层模块IClient的实现类。*/ public class Client implements IClient{
/*调用下层Server的copy()方法。但是下层必须知道要通知谁(一个
IClient的引用) 因而在Server(IClient)中传递this。*/
public void call() { new Server(this).copy();//传递this } /*回调方法。下层模块执行时,传回一些数据。*/
@Override public void callback(int i) { System.out.println(i+\"0%\"); } }//class Client
package API.event;
import API.event.lower.Server;//使用 public class TestCallback{
public static void test(){ new Client().call();}
public static void foo(){ new Server(new Client()).copy(); } }
这个例程说明:回调方法是某个上层模块实现的某个功能,但是所有的上层模块都不会直接调用它,设计它的目的就是为了下层模块的反向“调用”。
另外,回调(callback)是一个名词,而非动词(call back)。并非意味着“我调用你,你调用我”,例如任一上层模块如TestCallback可以调用下层Server的copy(),但是它不提供回调,而由Client提供。
3.从好莱坞原则角度解释白马非马
“白马非马”,白马替代马之后,他只能够是马而非白马,白马丧失了自己的特色,不能再将白马作为白马。在按照对象的一般概念,子类是一个父类,父类动物有eat()方法,那么我们喂狗吃东西的时候即new Dog().eat(),有没有人说:“我为动物吃,而动物调用狗的吃方法“,”我又喂动物吃,而动物调用猫的eat()方法?”这显然是荒谬的。这也就是“白马非马”在好莱坞法则中的应用,父类不需要知道子类的附加操作,父类也不需要调用子类的操作。
设计模式
一、用类图和Java代码来说明策略模式的基本结构、意图和类图、角色(10) 类图、基本结构、意图、角色见书P208 Java代码:针对角色的JAVA 代码
环境角色类:public class Context { //持有一个具体策略的对象 private Strategy strategy;
/ * 构造函数,传入一个具体策略对象 * @param strategy 具体策略对象 */ public Context(Strategy strategy){
this.strategy = strategy; } /*** 策略方法 */
public void contextInterface(){ strategy.strategyInterface();} } 抽象策略类:public interface Strategy { /** * 策略方法 */ public void strategyInterface();}
具体策略类:public class ConcreteStrategyA implements Strategy { @Override public void strategyInterface() { //相关的业务}}
1. 2. 3. 4. 5. 6.
7. 8. 9. 10. 11. 12. 13. 14. 15.
public class ConcreteStrategyB implements Strategy {
@Override public void strategyInterface() { //相关的业务}} public class ConcreteStrategyC implements Strategy {
@Override public void strategyInterface() { //相关的业务 }} 针对意图的JAVA代码:
假设现在要设计一个贩卖各类书籍的电子商务网站的购物车系统。一个最简单的情况就是把所有货品的单价乘上数量,但是实际情况肯定比这要复杂。比如,本网站可能对所有的高级会员提供每本20%的促销折扣;对中级会员提供每本10%的促销折扣;对初级会员没有折扣。
根据描述,折扣是根据以下的几个算法中的一个进行的:算法一:对初级会员没有折扣。算法二:对中级会员提供10%的促销折扣。算法三:对高级会员提供20%的促销折扣。使用策略模式来实现的结构图如下:
源代码:抽象折扣类:public interface MemberStrategy {/* 计算图书的价格 * @param booksPrice 图书的原价* @return 计算出打折后的价格 */
public double calcPrice(double booksPrice);}
初级会员折扣类:public class PrimaryMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println(\"对于初级会员的没有折扣\"); return booksPrice; }} 中级会员折扣类:public class IntermediateMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) {
System.out.println(\"对于中级会员的折扣为10%\"); return booksPrice * 0.9; }}
高级会员折扣类:public class AdvancedMemberStrategy implements MemberStrategy { @Override public double calcPrice(double booksPrice) { System.out.println(\"对于高级会员的折扣为20%\"); return booksPrice * 0.8; }}
价格类:public class Price { //持有一个具体的策略对象 private MemberStrategy strategy;
/** 构造函数,传入一个具体的策略对象* @param strategy具体的策略对象*/ public Price(MemberStrategy strategy){this.strategy = strategy; }
/*计算图书的价格* @param booksPrice图书的原价@return计算出打折后的价格*/ public double quote(double booksPrice){
return this.strategy.calcPrice(booksPrice); }}
客户端:public class Client {
public static void main(String[] args) { //选择并创建需要使用的策略对象 MemberStrategy strategy = new AdvancedMemberStrategy();
Price price = new Price(strategy); //创建环境 double quote = price.quote(300);//计算价格 System.out.println(\"图书的最终价格为:\" + quote); }} 二、比较策略模式和命令模式
策略模式 把易于变化的行为分别封装起来,让它们之间可以互相替换, 让这些行为的变化于拥有这些行为的客户。GoF《设计模式》中说道:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可于它们的客户变化。
Command命令模式是一种对象行为型模式,它主要解决的问题是:在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”的问题。
GoF《设计模式》中说道:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
从这点看:
策略模式是通过不同的算法做同一件事情:例如排序;而命令模式则是通过不同的命令做不同的事情,常含有(关联)接收者。目标不同!
命令模式是含有不同的命令(含有接收者的请求):做不同的事情;隐藏接收者执行细节。常见菜单事件,而策略模式含有不同的算法,做相同的事情;区别在于是否含有接收者。命令模式含有,策略模式不含有。命令模式中的命令可以单独运行
打个比喻就是:命令模式等于菜单中的复制,移动,压缩等,而策略模式是其中一个菜单的例如复制到不同算法实现。
BLOG策略模式(多态结构、类层次、封装方法)P208
策略模式以类层次定义和封装算法集:父类定义接口,子类给出实现。【策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法于使用它的客户而变化,也称为模式(Policy)。】从封装算法集的角度看,策略模式就是设计一个类层次。策略模式仅仅简单地使用了多态技术。正因为策略模式仅仅简单地使用了多态技术,因而策略模式成为其他模式常用的一个基本技术。也可以说,策略模式没有技术含量。
2. 将策略从环境类中分离出来
一方面,子类型是必须有的,代码大家自己写;另一方面,通过IoC工具,Context不需要知道MyStrategy的子类型,因而,不画出各子类型不影响读者对该模式的理解。
策略模式的基本内容,就这么一点。 策略模式仅仅封装算法,提供新的算法插入到已有系统中,以及老算法从系统中“退休”的方法,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。
命令模式(接口、调用与被调用)P1
我的定义是:以封装普适方法的命令类层次为桥梁,将通常C/S结构的C与S解耦。 既然命令模式使得C仅仅依赖于Command,它不知道S为何物,也不知道S的接口,所以,
C下达的一系列命令,你可以组合成一个队列、可以组合成一个批命令;也可以反之,将C下达的一个命令分解成若干具体的命令;
对于命令执行前后的变化加以监控,你可以实现undo或redo;如果命令只是改变一个
页面的颜色,你很容易undo/取消操作;如果命令导致手榴弹炸了一个房屋,omg,你undo就很麻烦。
1.无视被调的方法名和消息接收者
如果Invoker希望无视被调的方法名如Receiver.m()和m1(),显然需要某个众所周知的、一般性的方法名,例如execute ()、run()、go()等对目标方法进行间接的调用。如:publicvoid execute (Receiver r){
r.m(); }而这种间接调用,不应该在Receiver的类体中编写。否则execute()间接调用m(),execute1()间接调用m1(),就相当于改变了方法名。为了间接调用,需要设计一个类X。假设X知道消息接收者是谁,即execute (Receiver)所需要的参数X自己搞定,则Invoker将不知道Receiver为何物。因为Receiver有方法m()、m1()或更多,需要设计类似X的X1、X2封装m1()、m2(),它们用execute ()或go()对目标方法进行间接的调用。统一地,这里使用execute ()。
2. Command接口
自然而然地,可以将X、X1等类泛化,得到它们的父类型Command。 例程 3-15泛化
package method.command.get; public interface Command{ public void execute();
}// public class X implements Command,略 package method.command.get; public class Invoker{
public static void test(){
Command c = (Command)tool.God.create(\"3-15-Command\"); c.execute(); c =
(Command)tool.God.create(\"3-15-Command1\");
c.execute(); } }
于是,在Command的实现类确定消息接受者时,获得如下结构。
命令模式的简化结构
★函数接口Command是一个一统江湖式的、关注一个操作的适配器模式的目标角色,完成消息发出者与执行者(Invoker/ Receiver)的解耦。
3.命令模式之2 Invoker Vs. Client
当程序中直接编写下达命令的语句如new Cmd1().execute()时,通常会将调用者与客户类合二为一。
在GUI程序中,下达命令的语句通常包含在底层框架中,或者说底层框架包含了调用者,这时程序员编写的程序都是客户类。GUI程序中,由系统作为命令调用者在某种界面事件发生时自动调用命令。具体命令类自己实现所有功能,不需要额外的接收者。在此情况下,既可以说应用了命令模式(编写了两个子命令),也可以说简单的应用了回调(编写了两个包含回调函数的类)。
yqj2065个人认为,命令模式的概念还是小一点好:具体命令指定执行者(可以是抽象类型)和被调用的方法,将C/S结构的C与S解耦。
GoF的:“将(客户端的)请求封装为一个对象,从而使你可用不同的请求对客户进行参数化”。过于泛泛而谈。“将(客户端的)请求封装为一个对象\",参见方法类型化,是Java接口的一般意图;客户下达的cmd.exe(),在多态的场合都是”用不同的请求对客户进行参数化“。当不需要接收者的时候,我们甚至可以把AbstractAction的
actionPerformed(ActionEvent evt)视为一个策略,也是可以的,难道我们说此例程使用了策略模式??不管怎么说,GUI中的这种应用,是命令模式的典型案例。如果你的程序中多处使用某种按钮如Exit,可以使用命令模式编写一个命令,而不是随地使用匿名类。
三、用类图和JAVA代码说明模板方法的基本结构和意图。P214(意图和结构见书)
类图:
JAVA代码:模板方法模式代码实现 /// 抽象类
public abstract class AbstractClass { // 一些抽象行为,放到子类去实现 public abstract void PrimitiveOperation1(); public abstract void PrimitiveOperation2();
/// 模板方法,给出了逻辑的骨架,而逻辑的组成是一些相应的抽象操作,它们推迟到子类去实现。 public void TemplateMethod() {
PrimitiveOperation1(); PrimitiveOperation2();
Console.WriteLine(\"Done the method.\"); } } /// 具体类,实现了抽象类中的特定步骤 public class ConcreteClassA : AbstractClass {
/// 与ConcreteClassB中的实现逻辑不同 public override void PrimitiveOperation1(){
Console.WriteLine(\"Implement operation 1 in Concreate class A.\"); } /// 与ConcreteClassB中的实现逻辑不同 public override void PrimitiveOperation2(){
Console.WriteLine(\"Implement operation 2 in Concreate class A.\");}} /// 具体类,实现了抽象类中的特定步骤 public class ConcreteClassB : AbstractClass{ /// 与ConcreteClassA中的实现逻辑不同 public override void PrimitiveOperation1(){
Console.WriteLine(\"Implement operation 1 in Concreate class B.\");} /// 与ConcreteClassA中的实现逻辑不同 public override void PrimitiveOperation2() {
Console.WriteLine(\"Implement operation 2 in Concreate class B.\");}} 客户端代码: class Program{
static void Main(string[] args){
AbstractClass c;// 声明抽象类
c = new ConcreteClassA(); // 用ConcreteClassA实例化c c.TemplateMethod();
c = new ConcreteClassB();// 用ConcreteClassB实例化c c.TemplateMethod(); Console.Read(); }}
四、1.子类如何对待父类方法的实现(复用)《编程导论》P138 2.用JAVA代码演示强制子类改进语义的改写(改写、替换)(5) ★模板方法模式:父类定义模板方法,子类改写流程的一部分。
模板方法模式(templatemethod pattern) 本身的思路很简单:模板方法定义了一个算法的框架或模板,本模式得名为模板方法模式;父类型Sup(暗示supertype)提供模板方法和可复用的稳定步骤;可变的部分,则由子类延迟实现。
[GoF 模板方法模式]:定义一个算法的框架,将它的一些步骤延迟到子类中。模板方法模式使得子类可以在不改变一个算法的结构时重定义该算法的某些特定步骤。
受的实现继承
任意一个涉及固定流程的日常生活例子,都可以演示模板方法模式。本节使用一般性的例子Sup类。类Sup中的模板方法templateMethod()定义了一个算法的框架。模板方法可以调用的方法,包括步骤方法、其他类中的方法、本类中的一些具体方法、某些工厂方法等等。
通常,templateMethod()以及不变步骤的代码如step1()由final修饰,不允许子类篡改。更彻底地,不变步骤的代码可以设计为private,如果它不被子类和外界使用的话,如方法privateStep。
package method.templateMethod; import static tool.Print.*; public abstract class Sup{
public final void step1(){ pln(\"Sup.step1()\"); } public abstract void step2();
public void step3(){pln(\"Sup.step3()\"); }//默认实现,警惕 public void step4(){ } //
private void _step (){pln(\"Sup.mmm()\");}//模板方法的某些部分步骤不需要外界和子类知道
public final void templateMethod(){
step1();step2();step3();this. _step () ; step4(); } }
模板方法模式中,值得讨论的问题是:如何有效地控制和合理地使用实现继承
[编程导论·4.2.3接口继承 Vs. 实现继承]中,强调了接口继承(协议继承)和抽象方法的重要性,同时解剖了实现继承的缺陷,列举了子类对待父类方法的5种形式:
直接继承,如final方法、不准备override的其他方法。 改进语意的改写。子类的改写方法中必须调用父类相同签名方法,super.m ()。 替换语意的改写。完全以新的实现替换,体现了对父类方法的漠视。 空方法的改写。没有代码复用的诱惑,一种特定情况下使用的设计技巧。 接口继承。对抽象方法的继承,无代码可以复用。 这5种形式中,仅前2种复用了父类代码。合理地使用实现继承的典型例子,是子类直接继承父类的final方法。将各子类同的行为提取出来并集中到一个父类中,以避免代码重复。这一思想被称为“代码向上(父类)集中”,是面向对象程序设计中常用的技术。通常用final来凸显稳定的代码的不必修改特点。如果实在需要,父类也可以给出基本实现——原则上,需要在文档中注明,子类应该改进语意的改写(至少调用父类的代码一次),而避免替换语意的改写。但是,子类设计者可能忘记调用父类的代码,强制子类设计者采用改进语意的改写的一个技巧是设计一个小的、private的模板方法_step3()。
package method.templateMethod; import static tool.Print.*; public abstract class Sup{ //其他代码略
// public void step3(){pln(\"Sup.step3()\"); }//默认实现,警惕 public void step3(){}//重构
private void _step3(){//模板方法 pln(\"Sup.step3(),base\"); step3(); }
public final void templateMethod(){ step1();step2();_step3();// } } 五、用类图介绍责任链模式基本结构和意图,角色 P147 Java代码介绍责任链模式的变体(请求处理和不处理)(8) 讨论职责链各个结点(处理和不处理,权限大小,基本介绍) 责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。责任链的结构:抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
代码;//抽象类public abstractclass IHandler{ private IHandler next;//successor public void setNext(IHandler next){ this.next = next; }
public void handle(int day){
if(next !=null){ next.handle(day); } else{System.out.println(\"nohandler\");} } } //具体类public class H2extends IHandler{ @Override public void handle(int day){
if (day<=6 &&day>1){//some Condition
System.out.println(\"ok!Handler is \"+this.getClass().getName()); } else{ super.handle(day); } }} public class H1extends IHandler{
@Override public void handle(int day){ if ( day>6){//some Condition
System.out.println(\"ok!Handler is \"+this.getClass().getName()); } else{ super.handle(day); } }} //测试类 public classtest{
public static void main(int x){ IHandler h2=new H2(); IHandler h1=new H1(); h1.setNext(h2); h1.handle(x); } }
处理权限大小问题:请求可以按照处理权限从低到高的方式上交,也可以按照分段(权
限平行)的方式或向下授权的方式处理。例程中演示的,其实是分段的方式。
六、用类图介绍状态模式的基本结构P201(图在上面) 七、用JAVA代码来演示以多态来重构分支
状态模式和策略模式、工厂方法模式一样,以多态来重构分支结构。
重构的要点:①以状态类State替代boolean、int、枚举类型或类型(注意这种情况)的分支判断参数,显然有多少分支,State将对应有多少子类。②状态类State将所有具有上述分支判断的方法,提取为自己的接口,State的子类分别给出配套的实现。
例程 4-4 State的子类们 package property.state; public interface State{
public String sayHello();
public String sayGoodbye(); } package property.state;
public class FriendState implements State{ @Override public String sayHello(){ return \"你好,我的朋友\"; } @Override public String sayGoodbye(){ return \"再抱抱!\"; } }//OpposingState略
package property.state; public class Man2{
private State state;
public String sayHello() {
return state.sayHello(); }
public String sayGoodbye() { return state.sayGoodbye(); } public static void main(String[] args) { Man2 one = new Man2();
one.state =new FriendState();//isHappy = true System.out.println(one.sayHello());
System.out.println(one.sayGoodbye()); } }
在不考虑状态转换时,单纯从代码上看,状态模式的State封装多个方法,而策略模式仅封装一个方法。此时,
状态模式与策略模式的关系,如同抽象工厂与工厂方法的关系。虽然状态模式封装多个方法,与抽象工厂一样,不会出现[3.2.4方法类型化]的例程3-5那样的组合爆炸,因为多个方法在一个状态下是配套的,而非任意的组合。
八、简介分支对分支语句进行重构的三种方式(工厂,策略,状态)
策略模式与工厂模式从uml图上来说,基本一致。只是强调的封装不同。我们以工厂模式和策略模式的比较来讲解策略模式。策略(Strategy)模式在结构上与工厂模式类似,唯一的区别是工厂模式实例化一个产品的操作是在服务端来做的,换句话说客户端传达给服务端的只是某 种标识,服务端根据该标识实例化一个对象。而策略模式的客户端传达给服务端的是一个实例,服务端只是将该实例拿过去在服务端的环境里执行该实例的方法。
状态模式的应用可以应用于lf-else冗长的判断,在《大话设计模式》一书中,就是这样的应用;我们知道,if-else是一种状态的匹配。
比如: If(man.state.equals(State.passion)){ //workingHard }else if(man.state.equals(:State.tired)){ //haveARest }else if(„.){//otherActive}
当我们在执行这段程序的时候,我们的状态就要从第一个if-else开始匹配;那么,我们知道,注释中的语句是对应的不同的状态的,我们匹配的字段是决定我们处于那个字段的,state是man的属性字段,我们用这个man的属性字段来匹配一个状态的属性字段,一旦匹配了以后,则执行这个属性能胜任的行为,不匹配则自动转换一个状态,再执行匹配;
详细代码如下:
对象类:(状态的具有者) import org.state.IState; public class Man {
private String stateStr = \"tired\"; private IState state = new PassionStateImp();//把最常用或者最初状态放//在这里,就好像进入if-else的判断入口;
public void request() { state.handle(this); }
public IState getState() {return state;}
public void setState(IState state) {this.state = state;} public String getStateStr() {return stateStr;}}
状态类: 状态接口:
package org.state; import org.man.Man;
public interface IState { public void handle(Man man);} 亢奋状态:
package org.state; import org.man.Man;
public class PassionStateImp implements IState { public void handle(Man man) {
if (man.getStateStr().equals(\"passion\")) {
System.out.println(\"i am working hander\");//符合判断,则做出动//作 }else{man.setState(new TiredStateImp());//继续匹配下一个状态}}} 疲惫状态:
package org.state; import org.man.Man;
public class TiredStateImp implements IState { public void handle(Man man) {
if (man.getStateStr().equals(\"tired\")) {
System.out.println(\"i must have a rest\");
} else {System.out.println(\"i don't know what's wrong,i think i must be ill\");}}}
大家就会说,其实还是要进行if-else判断的,对没错,我们只是把if-else判断分成很多个小的if-else判断,在代码大全和重构两书里面,我们都看见过专家们对“冗长子程序“的担忧,认为子程序如果过长的话,代码就会有“坏味道”,子程序过长,就想问问自己是在写面的过程的代码,还是面向对象的代码,在上述的代码中,我们把state的类给抽象出来,方便以后对state进行扩充,就好比,我们这里的ill状态什么的不做,只是呻吟了一句“oh,I must be ill”;没有去打针,没有去看病,如果以后要实现的话,我们不得不去修改那些已经完全成型的代码,去看那个长长的if-else(虽然我们这里不长,但是你在代码大全里面应
该见过保险企业的保费的计算吧,)而我们这里很好的做到了开闭原则,只要做出简单的修改就可以了;主要的工作在于扩展state的illStateImp子类;这是状态模式的一种应用。
状态模式最经典的应该,还是在于行为改变状态,状态决定行为的应用场景,我们在策略模式里面,可以清晰的看见,采用那种算法,哪么就让context实现那种算法,其决定权在于我们,在于客服端,这个context本身不能决定,算法对象本身不能决定,因为他是死的;而在状态模式里面不同,如下:
一辆汽车开进停车场有一下流程:
1:准备进入停车场,到收费站停下;(检查状态) 2:允许进入
3:进入关卡状态)
4:停止车位 (已泊车状态) 5:开车出关卡 (装备出关状态) 6 允许出去 7 出关状态
在上述流程中,我们可见我们汽车的状态是不断变化的,状态的变化是行为影响的; 我们可以在状态的handle方法中间,根据客观条件和情况来改变状态比如在检查状态的handle处理过程中我们这样:
if(allowEnter){//set the state to enter state //大大方方的进去了 }else{//set the state to goBack state//只能回家或者走后门啦} 清晰的看见状态影响行为的过程。 九、什么是双分派 P223
十、说明表达式问题的两难困境(内层次和类型方法) 九.类型爆炸
1.方法类型化避免类型爆炸的类图
方法类型化——将某个或某些方法(准确的说,是方法头或接口)提升为类型,是方法型模式的共同技术基础和设计思路。
方法类型化避免类型爆炸的类图
1.
例程 3-2 重构Person类
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 method.typed; import tool.God; /**
* Person
* 避免类型爆炸. * @author yqj2065 * @version 0.1 */
public class Person{
private WalkBehavior walkStyle = null; private SayBehavior sayStyle = null; public Person(){
walkStyle = (WalkBehavior)God.create(\"3-2-WalkBehavior\"); sayStyle = (SayBehavior)God.create(\"3-2-SayBehavior\"); } public final void eat(){
System.out.println(\"用筷子\"); } //每人说自己的方言 public void say(){ this.sayStyle.say(); } //慢条斯理、风风火火等七八种实现方式
public void walk(){ this.walkStyle.walk(); } } package method.typed; public class App{
public static void test(){
Person one = new Person(); one.say();
one.walk(); } }
2.桥接模式
意图:将抽象部分与实现部分分离,使它们都可以的变化。★桥接模式控制类型爆炸,通过辅助类使得主体类的方法多态化。
特点:桥接模式的主要目的是将一个对象的变化因素抽象出来,不是通过类继承的方式来满足这个因素的变化,而是通过对象组合的方式来依赖因素的抽象,这样当依赖的因素
的具体实现发生变化后,而我们的具体的引用却不用发生改变,因为我们的对象是依赖于抽象的,而不是具体的实现。
桥接模式的主要优点源于多态的优点。
①主体类和辅助类可以地变化,此变化指遵循OCP的“两者都可以通过派生子类加以扩展”。按照Client代码的演示,Person与Region的子类可以进行任意的组合。
②外界接触的是Person的接口show()。
③桥接模式将多个维度可能导致的笛卡尔积式类型爆炸转换成辅助类的子类型的累积和。
结构:
3.主体类vs辅助类
多继承性普遍存在。类型X与许多的概念/类型之间存在Is-A关系,而且它的这些父类型彼此并没有必然联系,每一个父类型仅仅描述X的一个方面,X需要(应该)继承多个父类才是适当的设计。例如Person(的职业)与地域/Region (还可以有年龄、收入等类层次),是的、无关的类型层次,则“武汉教师”与它们都存在As-A关系。但是,Java中不允许类的多继承,一个可能的设计方案是:以Person类层次为基础时,将Region设计成接口,为每一种职业都提供一套各种地域版本。10个职业,来自30个地方,则需要300个类——类型爆炸。为了控制类型爆炸,可以以Person(的职业) 为主(主体类),而将另一个类型(辅助类)Region作为主体类的成员变量。
六.表达式问题
2.用类图,java代码结束访问者模式的基本结构
通过类图可以看到,在访问者模式中,主要包括下面几个角色:
抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序
中就是visit方法中的参数定义哪些对象是可以被访问的。
访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。 访问者模式的通用代码实现
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.
abstract class Element {
public abstract void accept(IVisitor visitor); public abstract void doSomething(); } interface IVisitor {
public void visit(ConcreteElement1 el1); public void visit(ConcreteElement2 el2); } class ConcreteElement1 extends Element { public void doSomething(){
System.out.println(\"这是元素1\"); } public void accept(IVisitor visitor) { visitor.visit(this); } }
class ConcreteElement2 extends Element { public void doSomething(){
System.out.println(\"这是元素2\"); } public void accept(IVisitor visitor) { visitor.visit(this); } } class Visitor implements IVisitor {
public void visit(ConcreteElement1 el1) { el1.doSomething(); } public void visit(ConcreteElement2 el2) { el2.doSomething(); } class ObjectStruture {
public static List List int a = ran.nextInt(100); if(a>50){ list.add(new ConcreteElement1()); }else{ list.add(new ConcreteElement2()); } } return list; } } public class Client { public static void main(String[] args){ List 33. e.accept(new Visitor()); } } } 访问者模式这种结构可以用于两种场景:①解决表达式问题;②处理整体和部分结构的操作。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- jqkq.cn 版权所有 赣ICP备2024042794号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务