[关闭]
@Seymour 2018-07-28T11:20:17.000000Z 字数 34106 阅读 2804

yqj2065的博客

设计模式


yqj2065的博客每天进步一点点...
[原]拍到什么程度
写《OOD实战(Java8)》最难的地方,在于OOD领域存在大量错误认识,而我们又必须建立完整的逻辑体系。

在讲解OOD的“正确”知识时,我就需要拍一下GoF、Robert C. Martin、Martin Fowler。对于GoF,我是心怀敬意的,但是Robert C. Martin的DIP,就要把它拍到粪坑中去,因为DIP论文,夸张滴说,错误无处不在(1.2 抛弃依赖倒置原则)。

但是写书时,应该拍到什么程度呢?

拍多了,看起来像个怨妇;拍少了,一些货又说:你说别人错,你指出来啊。

注意,我这里只拍大牛,不入流的货的垃圾文章或博客,我一个个拍,我要累得吐血个屁了。再说了,大量的网络上的错误通常不是错误的原创,你有错误,搞个原创的出来,也值得拍一下。

我的博客,我回顾地看了看,一样的,大把的垃圾文章。特别的,只要是我把它移到垃圾桶栏目中的,都是垃圾博客。当然,有些博客,我有时候是想到什么说什么,喜怒笑骂,不考虑逻辑和全面,可取的是该博客的某个点。总之,你可以来拍我。

很多知识的介绍,需要作者研究得通透,才能够考虑如何简单地讲给学生/读者听。当我自己不通透时,文章就会使人看不懂;所以,你看不懂我的博客的时候,提出来,对我是好事。GoF很多地方就不通透。

作者:yqj2065 发表于 2018/07/13 08:56:25 原文链接 https://blog.csdn.net/yqj2065/article/details/81025266
阅读:15
[原]2017-2018-2 OOD试卷AB
一、 编程论述题:框架设计者
1. 以SortTest和IntSort为例,说明什么是控制反转IoC(7分)
2.什么是抽象依赖原则,它与应用程序、框架的关系(5分)
3. 有一个接口如下
package util;
@FunctionalInterface public interface DoubleOP {
double op(double m,double n) ;
}
编写应用程序App,以DoubleOP 的实现类、匿名类和λ表达式提供回调函数,分别完成m-n、m*n*n和求m与n大值功能。(8分)
二、 论述题:Parnas原则
1. 什么是Parnas原则,它在软件设计中的地位(6分)
2. 什么是针对接口编程,什么是类的接口,Java中如何限定类的接口(8分)
3. 为什么说动态类型语言无法遵循Parnas原则(6分)
三、 编程论述题:装饰模式
1.以类图介绍装饰模式,介绍其意图、角色。(5分)
2.用Java代码介绍结点和自然数模型(7分)
3. 按照Num模型,讨论职业/ IProfession类层次。IProfession定义了方法say(String),其实现类教师/ Teacher、医生、律师……按照其职业习惯给出say(String)的实现,这些实现类相当于Num模型中的Zero。而才艺/Talent类层次(相当于NextOne),可以装饰基本对象,say(String)实现中,在必要的时候会说english、在必要的时候会唱几句……(8分)
四、 论述题:抽象依赖原则的使能工具
1. 为什么说注入器是抽象依赖原则的使能工具(7分)
2. 说明God使用中出现空指针异常的若干原因(7分)
3. 说明God的不足,并介绍Spring(6分)
五、 编程论述题:模板方法模式
1. 以类图介绍模板方法模式,介绍其意图、角色。(5分)
2.下面的抽象类Sum封装了求和函数getSum,(8分)
package templateMethod;
public abstract class Sum{
public final double getSum(int a,int b){
double sum=0;
for(int i =a;i<=b; i=next(i)){
sum+=item(i);
}
return sum;
}
public int next(int i){return i++;}
public abstract double item(int x);
}
为什么需要设计IItem、INext分别封装一个抽象方法?并设计4参数的模板方法
public final double getSum(int a,int b,INext iNext,IItem iItem){
//添加代码
}
3.说明对“模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为”的理解。(7分)

B卷
一、 编程论述题:行为参数化
1. 什么是行为参数化,为什么需要行为参数化(5分)
2. 为什么需要为Condition接口定义defaule方法and,or.并给出代码(8分)
3.什么是闭包,说明C语言在此的缺陷(7分)
二、 编程论述题:桥接模式
1.以类图介绍桥接模式,介绍其意图、角色。(5分)
2. 使用下面的接口F和G,构造复合函数(x+2)*(x+2)+1(8分)
public interface F {
public int f(int x) ;
}
public interface G {
public F g(F y);
}
3. 什么是高阶函数,其意义是什么(7分)

三、 论述题:面向对象与复用性
1.为什么以父类的代码复用为目的的继承,是错误的思考方式。(5分)
2.解释“作为复用机制,委派优先于继承”。(7分)
3.用Java代码举例说明子类复用父类代码的5种形式。(8分)

四、 论述题:控制反转
1.什么是好莱坞原则,什么是控制反转IoC?(6分)
2. 框架向上层传递数据时,可以采用通知或轮询,比较两者的优缺点。(6分)
3. 介绍C、Scheme和Java中设计框架的技术(8分)

五、 编程论述题:适配器模式.
1. 以类图介绍适配器模式,介绍其意图、角色。(5分)
2.假定接口Animal(生命) 有抽象方法eat()、move (),它的实现类有猫/Cat、狗/Dog等等;而第三方提供的(已经存在的)鸟/Bird类,有接口如eat()和fly();编写BirdWrapper,使得Client可以如下统一处理Animal。
package delegate.adapter;
public class Client{
public static void main(String args[]) {
Animal a= new BirdWrapper();
a.eat();
a.move();
}
}(7分)
3.包装类BirdWrapper与遗留类Bird,既可以是Is-A的继承关系,也可以Has-A的组合关系。编写上面一小题的另外版本的BirdWrapper,并比较两者的优缺点。(8分)

作者:yqj2065 发表于 2018/06/26 20:00:17 原文链接 https://blog.csdn.net/yqj2065/article/details/80820151
阅读:22
[原]《OOD实战(Java8)》难产中
虽然yqj2065自知不是一个很负责的老师,但是比起绝大多数老师,我算得上/自认为对得起我的学生。

我讲课的内容,全世界独一无二。你们不会在任何其他人那里获得,除了我的CSDN博客中,零零散散。

我也准备了大量例程、文字...但是将它们搞成一本书,还差得远。但是,我有更有趣的事情要做,身体也不宜久坐,写书挺无趣。最最重要的是,我对待出书,有洁癖般的要求。那就是当我死的时候,它应该有资格陪我进火葬场。我也不想写什么论文,我不能够写出好文章时,至少,我能够要求自己不生产除了自己没人看的垃圾。

所以,我的学生可能会长期没有合格的教材,GoF的《设计模式》还算不错的选择。要学生看《编程导论(Java)》,你们买的盗版书,和我没有经济关系,我没有精神负担,当然我也认为你们应该看它。《编程导论(Java)》,即使和我关系好的老师,我也建议他上Java课时不要用。因为我知道他肯定讲不好,而且其他班的学生在学其他的教材。《编程导论(Java)》的发行太差,使我对写一本好书没有太多热情。劣币驱良币。

《OOD实战(Java8)》难产中....所以,你们每一届的学生,学到多少是多少。说实话,你们的基础太差,可能有其他任课老师的原因;更重要的是你们不愿意在此花时间。

作者:yqj2065 发表于 2018/06/24 22:39:07 原文链接 https://blog.csdn.net/yqj2065/article/details/80795224
阅读:36
[原]我是OO砖家
yqj2065是OO砖家.

作为OO砖家,我不能够全面地把设计模式的东西讲给你们,一方面时间不够,另一方面,有些东西我研究得不够。

作为OO砖家,砖头在手,我都不屑拍国内的设计模式书籍的作者,都是垃圾书,有什么好拍的。

要拍的是GoF、Robert C. Martin、Martin Fowler这些货。
大兵 大兵 大兵

并且,要求学生一起拍。拍得不好,我挂你!
作者:yqj2065 发表于 2018/06/10 21:40:34 原文链接 https://blog.csdn.net/yqj2065/article/details/80645158
阅读:23
[原]同学们写学习笔记——认识IoC、DI和DIP
今天看见了一篇《依赖注入和控制反转的理解,写的太好了》,按照我的看法,这是中文学院的文科生写的东西,或者说,不懂IoC、依赖抽象原则、框架的人在说文解字。因此,要求同学们针对该文章,结合抛弃依赖倒置原则 ,写学习笔记,题目“认识IoC、DI和DIP”。

【2018.6:你们把中文学院的文科生写的东西复制后交给我?全部打回去。发火】

通过God创建对象,认清Spring的DI仅仅是工具箱,不是框架!Spring与IoC一点关系都没有。不要相信一些公司的网站的宣传文字,也不要相信Martin Fowler的文章Inversion of Control Containers and the Dependency Injection pattern中的一些说法。

另外:可参考(注意:不是要你从中知道什么是IoC、DI和DIP,上课讲的你不知道?你要说明该文章的问题)

轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)


下面的文章,错误的要点在于:

1)在Spring与IoC一点关系都没有的情况下,强行解释两种的关系。错误来源于Martin Fowler的著名文章Inversion of Control Containers and the Dependency Injection pattern。

2)“传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象”。很多这样的话,都是看见来不错,但是经不起推敲的。任何Client依赖B,如果改为Client依赖A,再由A调用B,是不是也称为IoC??,例如“传统应用程序是由我们自己在Client中主动控制去直接获取依赖B,也就是正转;而反转则是由A来帮忙创建及注入调用依赖对象B”。换言之,所有在Client和B中间添加中间类的结构,都可以称为IoC?极其无聊。

大量的文章包括Martin Fowler,按照他们对IoC的理解,强行解释Spring容器,但是IoC的含义不可能你用你的,我用我的。OOD是工科是科学,不是文学,想怎样解释就怎样瞎说。

------------------------------------引用原文如下,你搞清楚他在说什么吗?他在说段子。-----------------------------------------

学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解。

一、分享Iteye的开涛对Ioc的精彩讲解

  首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846

1.1、IoC是什么

  Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
  ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
  用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:

图1-1 传统应用程序示意图

  当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

图1-2有IoC/DI容器后程序结构示意图

1.2、IoC能做什么
  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.3、IoC和DI

  DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
  ●谁依赖于谁:当然是应用程序依赖于IoC容器;
  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

  IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

  看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。

二、分享Bromon的blog上对IoC与DI浅显易懂的讲解
2.1、IoC(控制反转)
  首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

  那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

2.2、DI(依赖注入)
  IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
  理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

三、我对IoC(控制反转)和DI(依赖注入)的理解
  在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。
  所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
  这是我对Spring的IoC(控制反转)的理解。DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。

四、小结
  对于Spring Ioc这个核心概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。如果有理解不到位或者理解错的地方,欢迎广大园友指正!

作者:yqj2065 发表于 2018/05/25 14:13:46 原文链接 https://blog.csdn.net/yqj2065/article/details/80450929
阅读:297 评论:10 查看评论
[原]OOD/设计模式学习环境
OOD/设计模式的主要使用语言为Java,开发环境为BlueJ;

使用的其他语言环境:

C语言:Pelles c

Scheme语言:DrRacket

作者:yqj2065 发表于 2018/05/17 13:28:16 原文链接 https://blog.csdn.net/yqj2065/article/details/80349490
阅读:33
[原]什么是闭包?
对于吃,人们有一个基本理解——填肚子。正是因为有一个公认的基础,所以人们容易理解吃饭/吃酒、吃苦/吃醋/吃亏、吃货、吃人。

我以Java作为通常的工作语言,因此yqj2065一般不使用闭包这个术语——我不希望你从Java中知道了闭包,在别的语言中要重新理解。但是,在讲行为参数化时,需要讲到嵌套函数,所以,这里给大家一个比较完整的闭包(计算机科学)的定义,作为“公认”基础。


2.1.4 柯里化和闭包

将函数作为返回值,C语言只能够返回已有的函数,因为它不支持嵌套函数。嵌套函数有什么重要作用呢?它涉及到几个术语:函数柯里化、闭包和偏应用。

柯里化/currying,指将多元函数转化为多个一元函数的连续调用。丘奇的λ表达式要求单参数。例如有数学函数f(x,y) =2x+3y,丘奇的λ表达式写作λx. λy. ( 2x+3y)。柯里化的理论意义,在于说明人们只需要研究一元函数,其所有结论和规则能够运用到多元函数。实际的函数设计中,多元函数更实用。
在函数柯里化过程中,可以了解闭包和嵌套函数的意义。使用Scheme,多元函数和多个单元函数可以方便地转化。

(define F
(lambda ( x y)
(+ x y)
)
);;; 多个单元函数。或者
(define (curryingF x)
(lambda ( y)
(+ x y)
)
)

curryingF的返回值,即闭包。

((curryingF 2) 3) → 5

((curryingF 2) 3)的计算过程,先计算(curryingF 2) 返回一个匿名函数,然后该匿名函数以3为实参进行计算。这个匿名函数为一个重要特点:它带有一个数据2。

★闭包/closure ,指一个作为(外包函数)返回值的函数,它捕获外包函数的变量。

在Java中,嵌套函数不一定要作为返回值,所以需要将定义扩大,外界能够使用就OK。例如赋值出去,
1.闭包这一概念,存在许多误解,因此需要详细说明一下。

闭包是"带数据的行为",如同“玫瑰是红的”。有句话有两个问题。

(1)有些人会认为,X是红的,所以X是玫瑰。在计算机科学中,一个函数所依赖的变量全部在参数中,该函数是组合子,或称为纯函数(pure function)——它的输出值依靠和仅仅依靠其输入,对于相同的输入总是返回相同的值。非纯函数都是"带数据的行为",但是非纯函数不一定就是闭包。例如C语言中函数能够捕获全局变量,它没有闭包概念;Java类中的方法,通常不是闭包——使用成员变量的时候,只有局部类或匿名类的一些方法才可能是闭包——需要注意final的时候。有人看见"带数据的行为",就浮想联翩,登峰造极的说法:“对象是穷人的闭包”——毫无意义的东西。

(2) “玫瑰是红的”,然后呢?闭包是"带数据的行为"不错,但是为什么要强调"带数据"?一个嵌套函数使用外包函数的局部变量是最自然地要求,反映了对词法作用域的尊重——外包函数的局部变量的作用域覆盖嵌套函数。但是,当外包函数执行完毕,通常它使用的局部变量会被函数调用栈弹出。嵌套函数如何才能够使用将被弹出的局部变量呢?因此需要某种机制将这些局部变量加以保存。这个"某种机制"就是强调"带数据"的原因。事实上,程序员需要知道的是闭包的作用,即在函数内部定义并返回新函数。反而不"带数据"的嵌套函数,很难想象为什么会出现。

另一方面,对特别保存下来的局部变量,其值能否改变呢?

Java中要求局部变量的值不可修改,Java8之前在匿名类中要使用final修饰,Java8后匿名类、lambda表达式中可以省略final,要求为事实上不能被重新赋值。C#中允许修改局部变量的值,闭包捕获的将是局部变量的最终值。"最终值陷阱"在Java中也存在,如果一个匿名类、lambda表达式使用了成员变量,因为成员变量可变,匿名类/lambda表达式(含闭包)捕获的成员变量的最终值。

  1. Java社区注意

Java程序员要注意,Java社区通常把lambda表达式称为闭包。或者说,在瞎用闭包概念。例如,Java8将闭包引入Java。事实上,lambda表达式能够做的事情,Java7都能够做到。Java8并不是将闭包引入Java,Java7就支持闭包。只不过“Java8将lambda表达式引入Java”。

2.1.5闭包的用途

组合函数

延迟计算

作者:yqj2065 发表于 2018/01/27 19:57:03 原文链接 https://blog.csdn.net/yqj2065/article/details/79182683
阅读:129
[原]编程类课程的问题
编程类课程的问题

从C语言教学开始,我们的教学体系就不是在讲授如何编程,而是在讲一门语言的语法,要求学生死记硬背一些乱七八糟的东西;这些传统,一直延续到C++和Java教学中。

很多人学完C,不知道什么是过程编程范式,接口与实现分离,成天搞:变量与常量——这两个概念为什么放在一起讲、printf,i+++i、0[array]之类,一本书几百个#include 、main...

直到讲面向对象编程了,偏偏使用C++,而且一上来就是“Animal a;"。这个东西——说a,有的垃圾Java书籍中也称之为对象。我有理由怀疑,他们是不是真的不知道“Animal a;”干了什么。我与一个同事聊过,说OOD时Java中,我不允许学生new——因为要针对接口编程,因此建议C++中不讲或很晚讲栈中创建对象的内容,栈中创建对象,必然是具体类,它的子类有新数据,只得阉割...不敢多态...他说没有考虑过“Animal a;”是在栈中创建对象。

作者:yqj2065 发表于 2018/01/18 19:43:08 原文链接 https://blog.csdn.net/yqj2065/article/details/79100036
阅读:87
[原]考前提示

考试前请学生们注意:

1.本课程开卷,不限制你带任何书籍和资料。我不知道教务处有没有限制你们不得使用手机,但是我不建议你们用手机,因为你肯定不懂才到处查资料,而你查到的网上的资料,大多数我会认为是错误的。

2.不得大篇幅抄袭我的博客中的内容。你不会的,抄唐诗宋词比较好。抄我的,驴头不对马嘴,会让我心烦。

3.考试前,我不会整合博客内容。和考试关联度高的博客,我可能会暂时隐藏。

4.看清楚题目,不要丢三落四。

Good lock!

参考资料

2014-2015-2试题
(2017-2018-1)挂了4个

作者:yqj2065 发表于 2018/01/14 23:42:23 原文链接 https://blog.csdn.net/yqj2065/article/details/79060322
阅读:162
[原]1.2 抛弃依赖倒置原则
1.2.2 依赖倒置原则(DIP)是什么
Robert C. Martin在1996提出DIP,并在几年后收入到他的著作《敏捷软件开发》中。随后,国内外有大量的基于DIP的文章、许多使用或介绍DIP的书籍。国内几乎所有都关于设计模式的书籍都介绍它。一个以其昏昏使人昭昭的东西,居然大行其道,简直令人匪夷所思。【所以,我对国内几乎所有设计模式的书,只会翻阅一下,说声 垃圾。】

1.可笑的第五步
依赖倒置?很多设计的初学者会问:“哪里体现依赖的倒置”?回顾项目1【为什么要你们自己先做一遍?我希望你们自己从代码中获得 清晰的认识】,整个项目可以出现下面的五步变化。

l SortorTest->BubbleSort。客户依赖具体的服务BubbleSort,地球人都知道这不好。

l SortorTest->IntSort<=BubbleSort。应用程序SortorTest遵循针对接口编程/抽象依赖原则,依赖抽象类型IntSort,而BubbleSort自然地依赖父类型。

l 【SortorTest->IntSort】。这一步是关键。如果需要将控制模块SortorTest设计成框架,可以将控制模块SortorTest和它必须依赖的抽象类型IntSort打包。控制模块SortorTest从应用程序(上层模块)变为框架(下层模块),为控制反转(Inversion ofControl)。最好能够提供JavaDoc,并且将方法名sort 改成soooooort。

l Main->【SortorTest->IntSort】<=BubbleSort。(其他程序员)使用框架。

l 带main的BubbleSort,BubbleSort->【SortorTest ->IntSort】<=BubbleSort。与第一步“依赖倒置”。

的确,从第一步到第五步,SortorTest->BubbleSort变成了BubbleSort->【SortorTest】,依赖关系倒置了——依赖倒置就是从A依赖B,变成B依赖A。

但是,没有一个DIP的鼓吹者会承认这么简单的说法,因为没有人会为毫无意义的最后一步提出一个设计原则。DIP所言的倒置,本身就是胡扯,因此其鼓吹者只好随意发挥,如《HeadFirst 设计模式》【国内的垃圾书,我不想列举。因为我不在意它们。】对依赖倒置的解说甚至是“倒置你的思考方式”。
在应用程序设计和框架设计中,抽象依赖原则/ADP均有重要作用。在应用程序的编程中,可以把ADP视为一种规劝或忠告;而在框架设计中,依赖抽象类型则是条例和军规。

本节以对排序算法进行测试为例,说明ADP的重要作用。测试例程将有五个步骤的变化,其中涉及单一职责原则、针对接口编程/ADP、分层架构的框架设计(控制反转)等。Robert C. Martin 于1996在一个专栏上发表了依赖倒置原则(Dependency Inversion Principle、DIP),该原则是一个错误的尝试,他希望将针对接口编程和控制反转纳入其DIP中,形成了一个思路混乱的、不知所云的原则。

  1. DIP论文的错误
    或许DIP最早的翻译文章就是我在CSDN上帖出来的,当时虽然觉得该文章含糊,但没有太多人提到DIP,我也就将它等同“针对接口编程”。现在很多人提到它,就得仔细研究一下。RobertC. Martin的那篇现在居然找不到链接的原文,该文奇葩到了错误到处都是的地步。

下面说明其论文的错误。

(1)起点就错了。

其论文中写到:『为什么我要使用单词"倒置"(“inversion”.)。坦白地说,这是因为比较传统的软件开发方法——例如结构化分析和设计,倾向于创建这样的软件结构:高层模块依赖于低层模块,并且抽象依赖细节。的确,这些方法的一个目标在于定义一个子程序层次以描述高层模块如何调用低层模块,....。因此,一个设计良好的面向对象程序的依赖结构,对应于传统的过程式方法通常会形成的依赖结构,是"倒置"的』。

这暴露了该论文的立论基础已经错了。传统的过程式方法可以设计被调的函数库,也可以设计框架;设计良好的面向对象程序,可以设计庞大的工具箱类库,也可以设计更多的框架。在设计工具箱时,过程式和面向对象依赖结构一样;在设计框架时,过程式和面向对象依赖结构一样。

如果有人把女人中的好人和男人中的坏人加以比较,得到结论:女人与男人在人性上是倒置的,显然是极其荒谬的。在他眼里,设计良好的面向对象程序都是框架,而传统的过程式程序都是被调的函数库。可见,其论文的"倒置",从开始就是错误的。

框架=控制反转(Inversion of Control),框架设计,或者说Inversion ,与系统是不是采用“传统的软件开发方法”、“过程式方法”、“面向对象”,一点关系都没有。所以,其论文后面只得将错就错,但再怎样解释,只会使人不解。正因为DIP所言的倒置,本身就是胡扯,因此其鼓吹者不得不随意发挥,如《Head First 设计模式》对依赖倒置的解说甚至是“倒置你的思考方式”。

(2) 含混的高层-低层模块(High -low level modules)。

在介绍针对接口编程/抽象依赖原则时,不管采用什么术语,即不管是高层模块、控制模块或客户/Client,依赖者都不应该依赖“具体的”低层模块、步骤模块或Server,而应该依赖抽象类型IServer。使用控制模块通常意味着在框架场合讨论问题;而不论在分层场合,同层中或应用程序都使用C-S来进行一般性的讨论。

RobertC. Martin使用高层-低层模块,为什么他既不愿意将高层-低层模块等价于C-S,也不愿意使用分层架构中含义清晰的上层-下层?这种含义不清晰的术语最适合浑水摸鱼。

(3) 强说反转。

Robert C. Martin如何介绍其反转呢?首先写到:『Copy()模块,它包含高层策略,依赖于它控制的底层细节性模块』,再依赖抽象类型后写到:『然而Copy类根本不依赖"Keyboard Reader"和"Printer Writer"』,因此依赖性被反转了!换言之,Client依赖具体类Server变成依赖IServer就是他的所谓“反转 ”。并陈述依赖倒置的一般形式:

依赖倒置原理、DIP:

  1. High level modules should not depend uponlow level modules, both should depend upon abstractions.

  2. Abstractions should not depend upon details,details should depend upon abstractions.

这就相当于,实验1在介绍第2步遵循针对接口编程/抽象依赖原则时,他就将没有发生的,对第3步框架的描述“反转”提前使用了。

(4)高层的复用性。

Robert C. Martin为了其原则,预设了一个前提,高层应该设计成框架。他写到:『高层模块应该优于低层模块。...当高层模块不依赖低层模块时,高层模块可以被十分简单复用。正是这个原则,它是框架设计(framework design.)的核心。』

在SortorTest->IntSort例子中,高层模块SortorTest的确具有作为框架的可能性;但是Robert C. Martin自己给出的例子,Copy模块,Java程序员都知道,几乎没有人为特定的应用开发IO框架,而是使用Java的IO工具箱构造各种各样的应用。在大多数场合,所谓的高层模块通常不具备复用性。

(5)依赖从未反转。

不论在应用程序场合,还是框架场合,正确的设计都是SortorTest->IntSort。控制反转/IoC中的反转,指软件设计决定的控制权,由应用程序员转移到框架设计者手中。Robert C. Martin的反转,指Client从依赖具体类Server变成依赖IServer。所以,他的反转,既不能按照IoC的反转理解,也不能按照日常的反转——颠倒来理解。如果程序员习惯了依赖抽象类型,Robert C. Martin的反转永远不会出现。

(6) 东施效颦的倒置/反转

Client从依赖具体类Server变成依赖IServer,被Robert C. Martin称为反转。他自己也觉得不好意思。于是,他需要在分层结构中,将他的反转伪装成控制反转/IoC中的反转。他的反转于是出现3中含义:对传统的过程式方法的反转、对依赖具体类的反转,IoC的反转。他的反转随时变化,想怎样解释就怎样介绍。因为他都不知道应该如何解释反转。

(7) 什么是抽象-细节

Robert C. Martin的DIP中,最令人困惑的是他不说人话。什么是抽象-细节?如果读者认为抽象-细节是父类型-子类型,那么“抽象类型IServer不应该依赖子类型Server,Server应该依赖IServer”就是毫无意义的废话。抽象-细节其实模仿“针对接口编程,而不是针对实现编程”中的接口与实现。前面介绍过,“针对接口编程”中提到接口和实现,容易使人不适当地联想到Parnas原则/接口与实现的分离原则。“针对接口编程”中提到接口和实现,应该理解为Java抽象类型(含Java接口和抽象类)和实现类。

而Robert C. Martin在模仿“针对接口编程,而不是针对实现编程”的同时,他又搞不清与Parnas原则中接口与实现的不同。他写到:『C++中,实现并不是自动与接口相分离的』,『应用的高层(policy)没有与低层模块相分离;抽象没有与细节相分离。没有这样的分离,高层自动的依赖于低层模块,并且抽象也自动的依赖于细节』,说明他在想应用Parnas原则,但是Parnas原则中,可以说具体类的接口(抽象)没有与实现(细节)相分离,但是从来没有“抽象也自动的依赖于细节”的说法,只讨论接口与实现分离还是不分离,从来没有接口依赖还是不依赖实现的说法。

所以,Robert C. Martin的抽象-细节,既不是“针对接口编程,而不是针对实现编程”的接口与实现,也不是Parnas原则中的接口与实现。从两套接口与实现中,你都无法明白Robert C. Martin想表达什么。

(8)Robert C. Martin还捏造了一个SOLID作为面向对象设计原则,D就是DIP,提起来就恶心。

草稿 抛弃依赖倒置原则 将被修改为吐槽版。

作者:yqj2065 发表于 2017/12/29 21:55:10 原文链接 https://blog.csdn.net/yqj2065/article/details/78935183
阅读:225
[原]动态类型+面向对象 = shit
动态类型+面向对象 = shit

1.动态类型
动态类型语言,属于看起来方便,用后麻烦的东西,我不喜欢它们。除非不需要考虑软件工程场合——如作为脚本语言。

动态类型+高阶函数,使得Scheme定义的op非常强大,如同酒驾的司机,非常狂野。

(define (op a b how-op)
( how-op a b) )
op可以对两个参数a,b进行“某种”how-op操作后返回一个值。然而,如果不看op的实现,应用程序员就不可能知道how-op应该是什么样的函数,也不知道op将返回什么结果。在当前实现下,应用程序员仅仅知道how-op有两个参数,how-op的返回值即op的返回值。

如果op实现如下

(define (op a b how-op)
(and (how-op a) (how-op b) ) )

那么,应用程序员仅仅知道,how-op有一个参数,返回值为布尔值,op的返回值也为布尔值。可以应用如下:

(op 1 3 (lambda(x)(> x 0))) ;;
(op #t #t (lambda(x) x)) ;;;

Scheme定义的函数,应用程序员无法脱离实现而仅仅了解其接口,或者说,Scheme程序不可能遵循接口与实现分离。【也就是说,用户看到一个函数,理论上,需要了解函数的实现。这是讨厌的事情。】

类似的Python语言,不可能出现针对接口进行抽象的、类似Java抽象方法或C++的纯虚函数的概念,也就不会有抽象类型。【因此,动态类型语言,在理论上就不支持抽象函数——你无法脱离实现定义接口。】
如果一门面向对象的语言,不支持抽象类型,除了称之为shit,没有其他的描述。

鸭子类型(duck typing)
“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”

动态类型语言不支持接口与实现分离,对于这种动态类型的面向对象语言,它很容易实现所谓的鸭子类型——因为该语言的参数不管类型。

function( duck ){
1+ duck.quack() ;
}
按照设计者的原意,function( duck )的参数是Duck类型,但是不管什么类型,周黑鸭也好,只要——按照上面的实现——某东西有quack(),并且返回值能够与1相加,就可以作为实参。
如果那一天Duck类型将duck.quack()修改了——反正Duck类型的quack()返回值类型也是不定的,也就是说仅仅修改了function( duck ):

function( duck ){
duck.quack() and true ;
}
或者duck.quack()改成了duck.jiao()

周黑鸭就需要修改其quack()的返回值类型或函数名。你不要怪Duck类型的设计者的重构,别人凭什么通知你个死鸭子。
常言道:动态类型一时爽,代码重构火葬场。

作者:yqj2065 发表于 2017/12/26 18:30:54 原文链接 https://blog.csdn.net/yqj2065/article/details/78905238
阅读:201
[原]为什么人们说Python容易?
在讲OOD时,我用了C、Java和Scheme的例子,对语言我比较了解。

有学生说Python学习起来很容易。Python有动态类型、面向对象、lambda表达式,它会容易?

对于科班出身的人,你说Python容易,我理解为:你能够很快掌握它的语法并用它做事情;

对于零基础学编程的人,你说Python容易,我理解为:你能够很快上手开始学习它的语法并用它写HelloWorld级别的例程。

我看见很多小朋友学围棋,人们都说学围棋很容易。但是作为野狐业余3段的我,觉得围棋好难——看看被狗虐惨的柯洁。

我认同“学Python容易”,但是,学Python不同于学围棋,学围棋可以作为业余爱好,而学Python,你至少要靠它吃饭。

我真的不能够理解,那么多工作后的编程小白,他们怎么有勇气学习Python!

作者:yqj2065 发表于 2017/12/24 12:16:59 原文链接 https://blog.csdn.net/yqj2065/article/details/78884335
阅读:277
[原]实验3 累积函数
关键词: 实验4 求和方法的一般化 , 模板方法模式,通用函数,高阶函数

若干个函数拥有相似的算法或代码结构,可以从中提取一个算法的框架,从而获得一般性的、通用的函数/方法 。

  1. 求和函数
    考虑数列或级数求和(或求近似值)的若干例子。
     求[a,b]之间自然数的和;//
     求调和级数前n项的和,H(n)= 1/1 + 1/2 + 1/3 + 1/4 + ... + 1/n
     求Pi,pi/8 = 1/(1*3)+1/(5*7)+1/(9*11)+...
     求函数的定积分
    程序员可以快速地编写上述问题的代码,如下面的例程4-1所示。
    package chap4.templateMethod.sum;
    import static yqj2065.util.Print.pln;
    /**
    • 按照SICP 1.3.1的命名,
    • sum_integers求代数和, sum_cubes求立方数的代数和,
    • pi求1/(1*3)+1/(5*7)+1/(9*11)+...的前n项的和
      *
    • @author yqj2065
      */

public class TestSum{
//1、若干的函数拥有相似的代码结构
public static void test() {
pln("代数和" + sum_integers(1, 10));
pln("pi=" + pi(10000));
/*pln("pi=" + 8*new Sum_pi().sum(1, 10000));
Accumulate.IItem item = x -> 1.0 / (x * (x + 2));
Accumulate.INext next = x -> x + 4;
double pi = 8 * Accumulate.getSum(1, 10000, next, item);
pln("pi=" + pi);*/
}

//////////////////////求[a,b]之间自然数的和//////////////////////////
public static int sum_integers(int a, int b) {
    int sum = 0;
    for (int i = a; i <= b; i++) {
        sum += i;
    }
    return sum;
}

//////////////////////////立方数的代数和//////////////////////////////
private static double cube(int x) {
    return x * x * x;
}

public static int sum_cubes(int a, int b) {
    int sum = 0;
    for (int i = a; i <= b; i++) {
        sum += cube(i);
    }
    return sum;
}

/**
 * 求Pi,the sum of a sequence of terms in the series
 * 1/(1*3)+1/(5*7)+1/(9*11)+...
 *
 * @param x
 * @return
 */
private  static double item(int x) {
    return 1.0 / (x * (x + 2));
}

public static double pi(int n) {
    double sum = 0;
    for (int i = 1; i <= n; i += 4) {
        sum += item(i);
    }
    return sum * 8;
}

//////////////////////////////////调和级数前n项的和
/**
 * 调和级数Harmonic numbers, H(n)= 1/1 + 1/2 + 1/3 + 1/4 + ... + 1/n
 *
 * @param n
 * @return
 */
public static double harmonic(int n) {
    double sum = 0.0;
    for (int i = 1; i <= n; i++) {
        sum += 1.0 / i;
    }
    return sum;
}

}
若干个求和函数拥有相似的代码结构,代码中不同/变化的部分主要有2处:步进和累加项,可变的两部分设计为抽象方法。注意,有些代码结构的不同部分,如求和函数返回值不同,则取更大的数据类型;参数个数不同,则取最多的个数;如求PI时返回sum*8,而乘以系数,可以留给调用者自己添加。

很容易从中提取通用的、一般性的方法,代码中不同/变化的部分主要有2处:步进和累加项,可变的两部分设计为抽象方法。例程4-2所示的模板方法getSum(int a, int b),可变的两部分设计为抽象方法并放在父类型Sum中,由Sum派生各种级数求和的子类型,例如Harmonic/调和级数。
package chap4.templateMethod.sum;

/**
* 模板方法模式
*
* @author yqj2065
*/
public abstract class Sum {
public final double getSum(int a, int b) {//template Method
double sum = 0;
for (; a <= b; a = next(a)) {
sum += item(a);
}
return sum;
}
public abstract double item(int x);
public abstract int next(int x);
}

class Sum_pi extends Sum {// pi/8
@Override public double item(int x) {
return 1.0 / (x * (x + 2));
}

@Override public int next(int i){
    return i + 4;
}

}

class Harmonic extends Sum {
@Override public int next(int i){ return i+1; }
@Override public double item(int x) { return 1.0 / x; }
}
这个例子符合[GoF•5.10]关于模板方法模式的定义,如果程序员需要Harmonic这样的、把两个方法实现放在一个类体中,编写独立的类(或者匿名类),是可行的选择。

但是需要注意的是,Sum存在2个抽象方法的组合问题,特别是考虑求函数的定积分时,item(double x) 的实现有无限的可能性。从设计角度看,程序员通常不在意GoF的模板方法模式中的抽象方法是否分离出去。换言之,更多时候,各种求和计算没有作为类而独立存在的必要,因此将Sum作为Context类而2次使用策略模式,重构得到新的类型Accumulate。
package chap4.templateMethod.sum;
public final class Accumulate{
public interface IItem {
double item(int x);
}
public interface INext {
int next(int x);
}

public static final double getSum(int a, int b, INext iNext, IItem iItem) {
    double sum = 0;
    for (; a <= b; a = iNext.next(a)) {
        sum += iItem.item(a);
    }
    return sum;
}

}

// TestSum
public static void testAccumulate() {
Accumulate.IItem item = x -> 1.0 / (x * (x + 2));
Accumulate.INext next = x -> x + 4;
double pi = 8 * Accumulate.getSum(1, 10000, next, item);
pln("pi=" + pi);
}

2.求积与累积函数
按照上面的样子,可以编写通用的函数getProduct()求积。

public static final double getProduct(int a, int b, INext iNext, IItem iItem) {
double r = 1;
for (; a <= b; a = iNext.next(a)) {
r = iItem.item(a);
}
return r;
}
比较求和与求积,代码中变化的部分有2处:累积变量的初始值(求和时为0,求积时为1)和累加计算的操作符。更进一步,从求和与求积中可以提炼出更一般性的函数——累积函数accumulate。
累加计算的操作符,分别为+和
。Java中不能够直接将+和作为函数的参数,需要用函数替换+和,可以使用例程3-1中定义的DoubleOP。

在getSum()基础上,可以利用下面的公式计算定积分。首先,需要将Accumulate所有int修改为double。【有些学生只copy代码不看文字,这里红色警示】

      函数f 的a到b定积分(不知道网页中能不能编辑数学公式) =  [f(a+dx/2) + f(dx + a+dx/2)+ (2dx + a+dx/2)+…] * dx

public static  final double accumulate(double defaultValue,DoubleOP how_op,double a, double b, INext iNext, IItem iItem) {
    double value = defaultValue;
    for (; a <= b; a = iNext.next(a)) {  
        value = how_op.op(value, iItem.item(a));
    }
    return value;
}

//计算定积分
public static final double integral(double a, double b, double dx, IItem f) {
    INext next = x -> dx + x;
    return dx * getSum(a + dx / 2, b, next, f);
}

附注:累积计算只有两种,因此可以用char how_op替换double defaultValue,DoubleOP how_op,编写5参数的accumulate().
3.添加过滤器
在accumulate()的基础上,可以在累积计算中添加过滤器(filter)功能,例如求[a,b]之间,每个自然数的立方,并含有7的数之和。

本文整合了我的若干博客内容,原理部分我没有写太多(避免学生考试时直接抄我的博客)!

作者:yqj2065 发表于 2017/12/24 10:06:56 原文链接 https://blog.csdn.net/yqj2065/article/details/78883690
阅读:254
[原]依赖丈母娘原则
近期整合静态工厂、工厂方法和抽象工厂模式。

在介绍工厂方法模式(3.3)时,我对工厂方法模式的合理情景/最佳实践,花了一点时间。

虽然从技术的角度,上面说明了从静态工厂方法到工厂方法模式的演变(这一过程对使用工厂方法模式充满误导),但是在God的例子中使用工厂方法模式,如果不采用参数传递的话,会出现如下的代码:

public staticfinal Locator locator = new MapLocatorFactory().getLocator();

这是非常典型的“为了模式而模式”的应用,与locator= new MapLocator()相比较,没有明显的好处。换言之,当God依赖Locator,出于初始化Locator的目的使用工厂方法模式,是不能令人信服的。

使用工厂方法模式的合理情景/最佳实践,我称之为依赖丈母娘原则。

★依赖丈母娘原则,Client依赖丈母娘,而丈母娘正好有一个工厂方法。
例如Client开车ICar,不管什么车Client都可以开,初始化ICar使用工具类God或依赖注入容器,而ICar有问题则需要其4S店处理,正好ICar有一个抽象方法

public I4S get4S();
public class Client{
public static void main(String[] args) {
ICar car =(ICar)God.create("Car"); //ICar car =new BBCar();
car.move();
I4S repair = car.get4S();
repair.doSomething();
car.move();
}
}
换言之,Client关注和依赖的,是工厂ICar,而工厂的产品I4S ,Client可以一无所知。需要的时候,ICar可以设计一个doSomething(),其中调用对应I4S实现类的同名方法(参考简单代理模式)。

注意到:《Head First 设计模式》在这里介绍了DIP,并给出的解答是“倒置你的思考方式”,所以:

依赖丈母娘原则 = (Head First 设计模式).DIP

读了一下《HeadFirst 设计模式·4工厂模式》,一个字评价烂,两个字,超级烂。

抽象类Pizza的代码忽略,它有一系列子类型如CheesePizza等; 而SimplePizzaFactory很容易理解,注意通常SimplePizzaFactory的工厂方法设计成静态的(没有理由让它为实例方法)。

从最终的用户PizzaTestDrive出发,假设它很关心披萨/Pizza,则其main中只需要代码:SimplePizzaFactory.createPizza("cheese");

《HeadFirst 设计模式》的该例程,使用了一点技巧:从一开始就考虑披萨店/PizzaStore依赖Pizza。但是即便如此,出于为PizzaStore初始化Pizza的目的,而使用工厂方法模式,仍然是难以令人信服的。
package init.factory;
public class PizzaStore {//
public final Pizza orderPizza(String type) {
Pizza pizza= SimplePizzaFactory.createPizza(type);
pizza.foo();//由此,我们假设PizzaTestDrive不直接调用SimplePizzaFactory
return pizza;
}
}

按照依赖丈母娘原则,正确的设计是PizzaTestDrive依赖披萨店/PizzaStore而PizzaStore正好作为Pizza的工厂。换言之,只有PizzaStore是Pizza的工厂才合理。

为了使得PizzaStore成为Pizza的工厂,作者开始说胡话了。在《加盟披萨店》中,。"现在我们考虑更多加盟店的需求:他们需要他们自己风格的口味,但是加盟店的一些业务流程又必须严格按照总店进行处理"。《我们已经有一个做法……》中,你的NYPizzaFactory等3个类和SimplePizzaFactory是什么关系?(类型爆炸);到《给披萨店使用的框架》,不管该作者说了些什么,总之目的就是使PizzaStore成为丈母娘。

你介绍工厂模式就介绍工厂模式,你要明确地说orderPizza(String type)是模板方法也可以,《允许子类做决定》写得超烂。最最讨厌的是Pizza是地域与风味的叉乘,如果有10个城市,5种风味,Pizza需要50个子类。
后面的懒得看了,,

作者:yqj2065 发表于 2017/07/09 23:23:32 原文链接 https://blog.csdn.net/yqj2065/article/details/74906918
阅读:365
[原]打包后的工具类 God+BlueJ+ClassLoader
工具类 God从开始的一个类,变成了使用策略模式的几个类;从项目中的一部分,变成了一个单独jar;

打包的yqj2065.jar在NetBeans环境中运行良好,有一天我在BlueJ中使用该yqj2065.jar,总是抛出NullPointerException。

我知道是路径问题,但是为什么NetBeans环境可以但是BlueJ中不行?虽然我一般用NetBeans,这个问题总在心里放着,真TM不舒服,而且一下两下还搞不定它。

近期有时间研究一下这个问题,发现是BlueJ的ClassLoader问题。ClassLoader问题其实在《编程导论(Java)·7.1类载入》中提到过,但是我已经忘记了。尴尬,或者说,当时我就没有太在意这个问题。p220写到:

注意:在BlueJ和控制台中运行,结果不同。在BlueJ中输出:java.net.URLClassLoader,而控制台中为sun.misc.Launcher$AppClassLoader。开发环境通常提供自己的装载器(注:选择某种装载器使用策略),而在控制台中myLoader 与ClassLoader loader = ClassLoader.getSystemClassLoader()的loader指向同一个对象。(这点差异不影响后面的介绍)
下面介绍一下我的探索过程,虽然走了一些弯路,但是走些弯路,可以看到一些也有意义的东西。
1.选择ClassLoader
第一阶段,在NetBeans和BlueJ项目中测试和选择ClassLoader。目标是访问 默认文件"my.properties"。
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(path))

是关键所在,因此将该语句分开

public  void testPropertiesLocator() {
     ClassLoader[] cls = new ClassLoader[]{
            ClassLoader.getSystemClassLoader(),
            this.getClass().getClassLoader(),
            Main.class.getClassLoader(),
            Thread.currentThread().getContextClassLoader()};
    for(ClassLoader x : cls ){
        URL resource = x.getResource("my.properties");
        pln(resource);
    }
    String cwd = System.getProperty("user.dir");
    pln(cwd);
}

NetBeans输出:

file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
D:\yqj2065\yqj2065

BlueJ项目输出:

null
file:/D:/path/my.properties
file:/D:/path/my.properties
file:/D:/path/my.properties
D:\path

基于classpath的相对路径,在BlueJ中需要排除ClassLoader.getSystemClassLoader()。

剩下的2个(2和3其实是一个东西),在BlueJ项目中都可以工作,

public static void testGetValue() {
    String str = new PropertiesLocator6().getValue("2065") ;
    pln(str); 
}

但是打包后,仅仅测试getValue("2065"),发现this.getClass().getClassLoader()不行了,它不能够正确访问测试项目的my.properties(它能够访问jar中的资源),
在测试项目中调用jar中testPropertiesLocator()和直接执行testPropertiesLocator()代码,可以明显发现,

public class Test{
public static void testGetValue() {
String str = new PropertiesLocator6().getValue("2065") ;
pln(str);
new Main().testPropertiesLocator();
pln("-------------------------------" );
ClassLoader[] cls = new ClassLoader[]{
Test.class.getClassLoader(),
Thread.currentThread().getContextClassLoader()};
for(ClassLoader x : cls ){
URL resource = x.getResource("my.properties");
pln(resource);
}

}

}
输出:

a.DDD

null
null
null

file:/D:/test/my.properties

file:/D:/test/my.properties
file:/D:/test/my.properties

当我以为问题搞定了,测试create,又出现NullPointerException。既然已经将类全名找到——a.DDD,用它创建对象还有问题?

这时,我才想起来翻书,我意识到是ClassLoader问题。

2.WhoLoadMe
类装载器系统采用托付模型,每个类装载器都有一个父加载器(与类层次无关)。细节请看《编程导论(Java)·7.1类载入》和随书代码。

在NetBeans使用yqj2065.jar的某个项目中,随便找一个类如Client,打印类装载器

    pln(Client.class.getClassLoader());
    pln(ClassLoader.getSystemClassLoader());
    pln(Thread.currentThread().getContextClassLoader());

输出:
sun.misc.LauncherAppClassLoader@55f96302
sun.misc.Launcher$AppClassLoader@55f96302

在BlueJ使用yqj2065.jar的项目中,随便找一个类如Test,打印类装载器

public static void testWhoLoadMe(){
    pln(Test.class.getClassLoader() );
    pln( God.class.getClassLoader() );
    pln( Locator.class.getClassLoader() );
    pln(Thread.currentThread().getContextClassLoader());
}

输出:
java.net.URLClassLoader@50ce9f3e
sun.misc.LauncherAppClassLoader@14dad5dc
java.net.URLClassLoader@50ce9f3e

虽然访问 默认文件"my.properties"时,我们指定了Thread.currentThread().getContextClassLoader()——java.net.URLClassLoader,但是反射创建对象时God的类装载器,却是AppClassLoader。真tm烦人。最简单地Class.forName(typeName).newInstance()不能够用了。

新版本如下:

package yqj2065.util;
public class God {
public static final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
private static final Locator locator =LocatorFactory.getLocator();
public interface Locator{
public String getValue(String path, String key);
default public String getValue(String key){
return getValue("my.properties", key);
}
public static String getPath() {
return System.getProperty("user.dir");
}
}
private static Object _create(String typeName){
Object obj = null;
if (typeName != null) {
try {
obj =classLoader.loadClass(typeName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
}
}
return obj;
}

public static Object create(String path, String key) {
    String typeName = locator.getValue(path, key); 
    return _create( typeName);
}

public static Object create(String key) {        
    String typeName = locator.getValue(key); 
    return _create( typeName);
}

}
而其他类没有什么变化,如
package yqj2065.util;

/**
*
* @author yqj2065
*/
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import static yqj2065.util.Print.pln;
public class PropertiesLocator implements God.Locator{
@Override
public String getValue(String path, String key) {
Properties props = new Properties();
try (InputStream is = God.classLoader.getResourceAsStream(path)) {
try (InputStreamReader isr = new InputStreamReader(is, "UTF-8")) {
props.load(isr);//从流中加载properties文件信息
}
} catch (java.io.FileNotFoundException e) {
pln(e.getClass().getName());
} catch (java.io.IOException e) {
pln(e.getClass().getName());
}
return props.getProperty(key);
}

}
打包后可以使用了。
但是,...................

①我有意留下的小尾巴:

private static final Locator locator =LocatorFactory.getLocator();
②我实在没有兴趣搞的东西,BlueJ的静态数据加载问题。在测试项目中使用了yqj2065.jar,打开BlueJ运行ok。
public class DDD{
public void foo(){
pln("okjhkkkkkkkjhlfkkdffddkk");
}
}
public static void testCreate(){
DDD dd = (DDD)God.create("2065");
dd.foo();
}
修改DDD,编译,再次运行:
java.lang.ClassCastException: a.DDD cannot be cast to a.DDD
(所以,运行前,请在BlueJ中重启JVM)

作者:yqj2065 发表于 2017/07/06 22:15:47 原文链接 https://blog.csdn.net/yqj2065/article/details/74603104
阅读:485 评论:1 查看评论
[原]装饰模式-撤销装饰
接Java 装饰模式(4.4)

装饰模式可以灵活地为基本对象添加功能,也可以撤销功能装饰。

假设需要撤销TSong装饰,则在IProfession中添加public abstract IProfession removeSongTalent();

IProfession的子类型给出实现:

Teacher:
@Override public IProfession removeSongTalent() { return this; }
TEnglish:
@Override public IProfession removeSongTalent() {
return new TEnglish(base.removeSongTalent());
}
TSong:
@Override public IProfession removeSongTalent() {
return base.removeSongTalent();
}

测试代码
IProfession p = (IProfession) God.create("Profession-decorator");//
p.say("类层次");
p = new TSong(
new TSong(
new TEnglish(
new TSong(
new TEnglish(p)))));
p.say("LSP");
p = p.removeSongTalent();
p.say("继承");

输出:

讲解[类层次]
讲解[E文(旋律(E文(旋律(旋律(LSP)))))]
讲解[E文(E文(继承))]

作者:yqj2065 发表于 2017/06/21 17:53:17 原文链接 https://blog.csdn.net/yqj2065/article/details/73551183
阅读:267
[原]4.1.3 模板方法模式(5.10)
【2018.6.2】通过设计累积函数,引入策略模式的扩展、模板方法模式(5.10)。


第4章 多重策略

4.1.3 模板方法模式(5.10)

例程4-2的抽象类Sum符合[GoF·5.10]关于模板方法模式的定义:

模板方法模式:定义一个算法的框架,将它的一些步骤延迟到子类中。模板方法模式使得子类可以在不改变一个算法的结构时重定义该算法的某些特定步骤。(Template Method Pattern: Define the skeleton of an algorithm inan operation, deferring some steps tosubclasses. Template Method lets subclasses redefine certain steps of an algorithmwithout changing the algorithm's structure.)

重构后的例程4-3的final类Accumulate,则提供了4参数的模板方法。严格地说,Accumulate有模板方法getSum(),使用了两次策略模式。但是,对于模板方法的一些步骤,是延迟到子类中、或者通过策略子类型提供、或者作为模板方法的参数,从设计角度看不需要太过区分。因此,

★模板方法模式:模板方法定义一个算法的框架,它的一些步骤可以延迟到子类中,也可以采用n次并行的策略模式。

或者说,采用传统的模板方法模式,两个(及以上的)方法实现放在一个类体中。当需要避免类型爆炸或实现方式过多时,采用n次并行的策略模式;如果模板方法中只有一个可变的部分,模板方法模式还原为策略模式。换言之,模板方法模式可以视为策略模式的简单推论:可变部分由一到多。【传统模板方法模式(5.10)、我的模板方法模式、策略模式】

1.可变步骤的设计

可变步骤通常有3中设计选择:

Ø 抽象方法

Ø 空方法

Ø 具体方法

上面介绍的item(int x),就是典型的设计选择——抽象方法。而对于next(int x),毕竟i++在循环计算中最为常见,如何设计INext呢?一种方式是INext仅仅提供一个具体的方法(default方法);同时,为了方便用户,还需要定义一个步进为1的对象用作getSum()的参数。但是,这种方案使得INext成为普通接口,需要其他实现时只能够采用匿名类而不能够使用简洁的lambda表达式。
public interface INext{
//double next(double x);
public static INext NEXT = new INext(){};
default double next(double x){
return x+1;
}
}
// TestSum文件中求从1到10的自然数的和
double r = Accumulate.getSum(1, 10, INext.NEXT, x->x);
pln("[1,10] 自然数的和=" + r);

该方案的替代,则INext不变,提供重载getSum(doublea, double b, IItem iItem)的3参数版本。


模式引申 4.3 模板方法模式(5.10)

4.3.1 代码向上集中

4.3.2 空方法

4.3.2 累积函数

4.3.4 关于GoF的(5.10节)

内容待填

相关博客被重构-移动 到垃圾桶。

[垃圾桶]Java 模板方法模式 模板方法模式、与n次策略模式 关系问题.整合到3.3.3。模板方法模式的定义

[垃圾桶]3.3模板方法模式2 +回调

[垃圾桶]模板方法模式(5.10) 脑残例子,如何有效地控制和合理地使用实现继承。整合到3.3.1

相关博客值得参考,整合完成后,也会移动 到垃圾桶。

高阶函数之函数作为参数 在求和的基础上补充了SICP练习1.31和1.32的东西。

Java8:λ表达式作为实参 和上面的博客是相同的东西。一个用Java8,一个用Scheme。整合到3.3.2.

累积函数。累加计算的操作符,分别为+和。Java中不能够直接将+和作为函数的参数,需要用函数替换+和*,可以使用例程2-2中定义的DoubleOP。

 public final double accumulate(double defaultValue,DoubleOP how_op,double a, double b, INext iNext, IItem iItem) {
    double value = defaultValue;
    for (; a <= b; a = iNext.next(a)) {  
        value = how_op.op(value, iItem.item(a));
    }
    return value;
}

吐槽大全 -GoF《设计模式》

写书和写博客是完全不同的。我写博客,想到什么写什么,该吐槽吐槽,兴致来了,管它逻辑不逻辑。

写书时,就需要将一系列的想法加以整合、逻辑上加以完善,各种可能需要考虑。

作者:yqj2065 发表于 2017/06/17 16:42:29 原文链接 https://blog.csdn.net/yqj2065/article/details/73381501
阅读:421
[原]IT界,不需要“下一个伟大的思想”
近期写《抛弃依赖倒置原则》,yqj2065不知道国内外有没有持相似观点的,因为没有找到。

突然间,我惊奇:如此稀烂地DIP,为什么没有人质疑呢?

联想ERP.CRM...AOP..云计算、大数据...是不是IT咨询界也在复制管理界的《下一个伟大的思想》?这是令人毛骨悚然的猜测。如果理工科也流行文科的套路,软件工程专家们像卖减肥药一样吆喝,那真是悲剧。

IT界,不需要“下一个伟大的思想”

作者:yqj2065 发表于 2017/06/10 23:34:43 原文链接 https://blog.csdn.net/yqj2065/article/details/73010625
阅读:373 评论:3 查看评论
[原]util.God -2
util.God的局限性:1)仅能够按照默认构造器创建对象;2)仅支持属性配置文件,3)不支持标注。

有同学问,yqj2065.util.God是Martin Fowler文章中的Service Locator吗?

总体而言,yqj2065比较烦引入太多概念。

【Service Locator模式背后的基本思想是:有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务。】

既然如此,util.God是Service Locator。

这个概念比较直观,但是”把ServiceLocator类实现为一个Singleton的注册表“等,我懒得看了,服务定位器的实现,简单地想,就有

工厂
God
...
JNDI(Java Naming and Directory Interface)

有同学问,我不想用配置文件,在项目和文件系统之间跑来跑去,好累啊。其实,Netbeans中在项目和文件系统之间跑来跑去,你操作很方便。
ok,不想用配置文件,你把键值对用Map保存就可以了。
package yqj2065.util;
import java.util.HashMap;
import java.util.Map;

/**
*
* @author yqj2065
*/
public class MapLocator implements Locator{
Map map = new HashMap<>();
{
map.put("IServer", "init.Server");
}
@Override
public String getValue(String path, String key) {
return map.get(key);
}

}
你的God要变一下:用策略。注意:
public class God {
private static final Locator locator = new MapLocator();

public static String getValue(String path, String key) {
    return locator.getValue(path, key);
}

...

作者:yqj2065 发表于 2017/05/28 19:24:59 原文链接 https://blog.csdn.net/yqj2065/article/details/72792856
阅读:342
[原]凡事都有例外-父类型依赖子类型
抛弃依赖倒置原则中,yqj2065对“抽象不应该依赖细节。细节应该依赖抽象”,描写成“一个毫无价值的废话,都能够包装成令很多人脑洞大开的原则”。

但是,凡事都有例外。

虽然极其罕见,有时候父类型需要事先知道其子类型。这是一种什么样的感觉呢?

我们看一个例子。定义自然数

package closure.style;
public abstract class Num {
@Override public String toString() {
return "zero";
}
}
public final class Zero extends Num{}

public final class NextOne extends Num{
Num pre;//predecessor
NextOne(Num pre){ this.pre = pre; }
@Override public String toString() {
return "new " +NextOne.class.getSimpleName()+ "(" + pre + ")";
}
}
//class Demo
public static void testNum() {
Num n = new NextOne( new NextOne( new NextOne(new Zero())));
pln(n);
}
这样的设计很普通。
假设,我们不想要Zero这个类,而是将它设计成Num 的静态成员,就会在父类型中出现子类型的名字——父类型依赖子类型。

public abstract class Num {
public static Num zero = new NextOne(null);
}
【有人说,你为什么不把zero作为NextOne的静态成员?哈,如果那样,怎么体现父类型依赖子类型呢,那不是没得玩了。】

父类型需要事先知道其子类型,的确是一个糟糕的设计体验。如果还和违反LSP搞在一起,就更糟糕。

但是,在特定情况下,这种搞法是没有办法的办法。

(作业题:请举出一个非常典型的例子。提示:WCF)

(放在4.1.1)

返回

作者:yqj2065 发表于 2017/05/26 13:06:16 原文链接 https://blog.csdn.net/yqj2065/article/details/72765460
阅读:364

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注