怎么把系统抽象化?
最近在看一些设计方面的知识,也看了一些面向对象设计中对事物抽象的概念。
但是我有些疑惑,看书上的例子,觉得他抽象的很是理所当然,但是我发现把这些概念移植到自己的系统的时候却有点糊涂了。
比如我的系统中有经销存的模块。
我想把他抽象化,怎么理解呢?
我是这么想的,不管是采购订单,库存入库单,还是销售订单,库存出库单。
我想他们都应该抽象成同一类事物,都有,增加(Add()),删除(Del()),修改(Update())等方法,
他们是不是应该都继承一个接口(IBill)呢?这个借口包含增加(Add()),删除(Del()),修改(Update())
而我们每个单据的类都应该实现这个接口的方法。
疑问来了,
这些方法都带有参数啊,而这个参数都是对应于每个表的实体类Modle,
如果参数定义不一样,是不是又说明他们的共性不存在呢?
呵呵,我就有疑惑了。。。
[解决办法]
如果是进销存的系统,请参考会计基本记账原则
所谓的抽象,不是让程序员去考虑如何操作数据库,而是让程序员去还原现实。
废话不多说了,回头看你的系统
1.采购订单,库存入库单,还是销售订单,库存出库单------这些实际都是业务凭证,不需要程序员多考虑,通通继承于业务凭证类即可
2.业务凭证只是凭证,相关业务逻辑 按会计记账原则称做会计分录
3 会计分录的前提是相关业务的业务科目,比如:材料采购,产成品销售,原材料库存,半成品库存,在制造品库存,成品库存,待入库材料采购库存
4 建立基本业务科目的分录关系 比如:
待入库材料采购订单:从现金账出,入待入库材料采购库存
已入库材料采购订单:未入库材料采购库存 出 ,入原材料库存
生产业务领料:在原材料库存 出,入在制造品库存
半成品入库:在制造品库存 出,入半成品库存
成品库存:在制造品库存 (+半成品库存) 出,入成品库存
======================
通过这些可以建立一些基本恒等式
5:记住一些基本会计结算准则,期末余额=期初余额+当期增加-当期减少
通过上面实际可以看出来 基本就是两个大的抽象类,一个是原始凭证类,一个是分录科目类
然后还有一个分录操作类。
class 原始凭证
{
凭证类型
分录操作
凭证本身的数据信息
}
[解决办法]
抽象不是想象出来的,是根据具体情况重构出来的,
抽象属于实现细节层面的内容,抽象是为了维护和扩展方便
设计的时候不要考虑抽象,先想好怎么用,最好在你的方法还没有写的时候就把客户端的代码写出来,从上往下一层一层地实现,在这个过程中寻找抽象点,这样写出来的系统里才简洁,才能做到不废话连篇
总的来说意思:抽象不是目的,是过程,一开始就想怎么抽象注定了失败!
[解决办法]
举例吧:
首先就可以抽象一个“订单”类,下面有两个子类:“采购订单”和“销售订单”。
“订单”就一定有共同的属性,比如:订单号、货物数量、总价等;
而“采购订单”就多了一个属性,比如叫做 Flag, 在采购订单的构造函数中就把它设为“采购”;
同理“销售订单”的Flag设为“销售”;
“订单”的行为一定包含一个 “下订单”,抽象出来,而由“采购订单”和“销售订单”分别实现。
再进一步,可以做一个“订单工厂”,负责产生 各种不同的订单实例,这里就可以用反射来实现了……
不需要代码吧?需要代码的话回个话。
[解决办法]
一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。
继承应当被看做是封装变化的方法,而不应当被认为是从一般的对象生成特殊的对象的方法。
一种可变性不应当与另一种可变性混合在一起。最好所有的继承结构不超过两层,不然就意味着将两种不同的可变性混合在了一起。
2。里氏代换原则
任何基类可以出现的地方,子类一定可以出现。
3。依赖倒转原则
要依赖于抽象,不要依赖于实现。
4。合成/聚合复用原则
要尽量使用合成/聚合,而不是继承关系达到复用的目的。
5。迪米特法则
一个软件实体应当与尽可能少的其它实体发生相互作用。(模块之间)
6。接口隔离原则
应当为客户端提供尽可能小的单独的接口,而不是提供大的总接口。
7。策略模式
将每一个算法用一个类封装起来,多个类实现同一个接口。用到哪种策略就实例化哪个类。
**************************************************************
第五章: 接口
它应当只用来声明一个新的类型
1。接口可以认为是继承的一种,叫做接口继承,而extends叫做实现继承。接口是个角色,而实现类是这个角色的演员。接口这个角色使之可以动态的智能的选择演员。
可插入性:
别的类只让这个角色做事情,而它不必关心是哪个演员做的。
2。用它来声明一个新的类型。在理想情况下,一个具体的java类应当只实现java接口和抽象java类中声明过的方法,而不应当给出多余的方法。
3。类型等级结构:java接口一般用来作为一个类型的等级结构的起点。
class本身是个类型,但它的起点应该是它所实现的接口。
混合类型:当一个具体的类处于一个类的等级结构中的时候,它再实现一个接口,它就成了混合类型,而不单单是等级结构中的子类型。混合类型是保证基于这个等级结构类型的可插入性的关键。
**************************************************************
第六章 抽象类
1。它代表一个抽象概念,它和接口一样,是一个等级结构的起点。但这点上它不如接口好。
2。继承都是从抽象类开始的。抽象类是用来继承的,而具体类不是用来继承的。具体类应该使用聚合的方法实现重用,而避免使用继承来实现。
例子:类B继承类A ===》写个接口或抽象类C,类A和类B实现C。
类“猫”继承“虎”===》他俩都实现“动物”接口
3。在一个从抽象类到多个具体类的继承关系中,共同的代码应当尽量移动到抽象类里。换句话说,共同的代码应当尽量向等级结构上方移动。
反之,抽象类应当拥有尽量少的数据,数据应放在等级结构低端,即具体类中定义。
4。依赖倒转原则:针对抽象类编程,不针对具体子类编程。
5。扩展超类,不要修改超类的方法,不建议使用重写。
*************************************************************
第八章 依赖倒转原则
1。要依赖于抽象,不要依赖于实现。把抽象层次作为复用的重点,而不是具体层次。
2。三种偶合关系:
零偶合:两个类没关系
具体偶合:发生在两个具体的类,由一个类对另一个具体的类直接引用。
抽象偶合:发生在一个具体类和一个抽象类,更具灵活性。(好)
客户类尽量和抽象层打交道。
3。依赖倒转原则的两种表述:
抽象不应当依赖于细节,细节应当依赖于抽象;
要针对接口编程,不要针对实现编程。(重点)
4。针对接口编程:应当使用java接口和抽象java类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
不要针对实现编程:不应当使用具体java类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等。
5。一个具体java类应当只实现java接口和抽象java类中声明过的方法,而不应当给出多余的方法。
6。在多数情况下,一个java程序需要引用一个对象。这个时候,如果这个对象有一个抽象类型的话,应当使用这个抽象类型作为变量的静态类型。 接口 x = new 类();
7。java语言中创建一个对象的过程是违背“开闭”和“依赖倒转”原则的。因为对象被创建之后,它就确定了,不能由抽象自动寻找使用哪个对象来处理问题。
8。缺省适配模式:抽象类的唯一优点是有具体类的部分实现,接口补充了抽象类的所有缺点,所以联合使用抽象类和接口是一个很好的选择。
************************************************************
第九章 接口隔离原则
1。使用多个专门的接口比使用单一的总接口要好。
从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的。一个接口应当简单地代表一个角色,而不是多个角色。多个演员可以同时演一个角色,就象孙悟空的孩儿一样。