抽象

抽象这个东西,提及来很抽象,其实很简单。java

WHAT

抽象是什么?按维基百科的说法:“在计算机科学中,抽象化(英语:Abstraction)是将数据与程序以它的语义来呈现出它的外观,可是隐藏起它的实现细节。”这个定义也许还有些“抽象”,举几个例子来看,它其实简单。 数据库

“作技术、如艺术”,计算机中的“抽象”与艺术中的“抽象”很有殊途同归之妙<br/>

“抽象”在咱们的平常工做和生活中比比皆是。例如,咱们常常会说,“我是一个开发”,“这事儿你得找产品”,这里的“开发”、“产品”,都是一种抽象。它们定义了“开发要写设计、写代码、写单测”、“产品要写ppt、写word、写excel”这样一种“语义外观”,可是它们本身并不会写代码或写文档,这些实现细节隐藏在“职位”之下、由具体的“员工”来完成。设计模式

看到职位,咱们就能知道这是作什么的;但具体怎么作?只有底下的员工最清楚。

在技术上,这样的例子更是俯拾皆是。例如,Slf4j提供了一个日志的抽象,它定义了“怎么打印日志”这个“语义外观”,可是它隐藏了实际打印日志的实现细节——是log4j、仍是logback?使用Slf4j时咱们是不知道的。还有,Jdbc的Driver、Connection和Statement定义了“怎么操做数据库”这个“语义外观”,可是它也没有实际去操做数据库。这些实现细节是由抽象之下的具体实现来处理的。异步

怎么打印日志?Slf4j会告诉你:logger.info(

即便是业务系统中,“抽象”的实例也随处可见。一个设计良好的接口就是一个业务的抽象,它定义了这项业务支持哪些操做。例如,咱们有一个短信签约接口定义了submit、sendCode、submitCode三个方法,本质上就是定义了“短信签约操做有三个步骤”这样一个业务抽象。至于每一个步骤都是如何实现的,这是底层逻辑的事情了——实际上,这三个步骤的底层用的是同一个方法。又如,咱们有一个冻结订单的接口,定义了frozenFlow、frozenLimit、forzenTransport三个方法,这也就是定义了“冻结一笔订单必须冻结Flow、Limit和Transport这三类数据”这样一个业务抽象。至于这三类数据具体如何冻结么——就咱们系统来讲,有的业务是直接逻辑删除,有的业务是把数据回滚到初始状态,有的业务则干脆不须要处理Transport数据——这又是底层逻辑须要考虑的事情了。 ide

因此,抽象是什么?抽象就是这样一个东西:它告诉了你本身能作什么、但不告诉你它是怎么作的。工具

就像抽象艺术:就算明白告诉你这是艺术,你也不明白它怎么就成了艺术了<br/>

WHY

设计出一个好的抽象,除了能隐藏底层实现以外,还有什么好处吗?咱们为何要在“抽象”这虚的东西上下功夫呢? 性能

借用另外一篇文章的话来讲:抽象设计得越好,代码就越简单易用;代码可替代性就越好;可扩展性就越好。 测试

简单易用

为何说抽象设计得越好、代码就越简单易用呢?由于一个好的抽象设计隐藏了它的底层实现,使得咱们在使用它的时候,不须要关注底层的细节。就比如开自动挡的车时不用关心离合换挡的事儿,开起来固然比手动挡要简单方便啦。优化

手动仍是自动?这是一个问题。

例如,咱们看看下面这个接口:设计

public interface QueryService{
    public Bean queryFromRemote(long id);
    public Bean queryFromLocal(long id);
}

这个接口提供了两个方法,两个方法的入参、出参都是如出一辙的,区别只在于方法名——以及名字所暗示的,是从“远程”查询、仍是从“本地”查询。若是调用方在使用时,确实须要区分数据来源,这个设计倒也无可厚非。可是,实际上调用这两个方法时,全部的代码都是这个样子的:

Bean bean = queryService.queryFromLocal(id);
if(bean == null){
    bean = queryService.queryFromRemote(id);
}
if(bean == null){
    throw new Excepton();
}

这样的代码出现了至少五次。啰嗦吗?啰嗦。麻烦吗?麻烦。闻着臭吗?臭。为何每次调用这个接口时都要这么写呢?由于这个接口把本身底层的实现——是从远程获取数据、仍是从本地获取数据——暴露出来了。换句话说,这个接口的抽象设计得不够好。若是咱们把这个接口设计成这样:

public interface QueryService{
    public Beean query(long id);
}

顺便,底层这样实现:

public class QueryServiceImpl{
    public Bean query(log id ){
        Bean bean = queryFromLocal(id);
        if(bean == null){
            bean = queryFromRemote(id);
        }
        if(bean == null){
            throw new RuntimeException();
        }
        return bean;
    }
}

那么,咱们就能够这样调用这个接口了:

Bean bean = queryService.query(id);

这样从新设计/实现过以后,使用起来是否是简单、方便多了?这就是良好的抽象设计的第一个优势。

可替代性

为何说抽象设计得好,代码的可替代性就越好呢?这一样是由于一个好的抽象设计隐藏了它的底层实现,不管咱们怎么更换实现细节,只要对外抽象不变,调用方都不受影响。这就比如咱们去银行柜台取钱:只要能把钱正确取出来,柜员是男是女、是胖是瘦、甚至因而活人仍是机器,这都无所谓。

看看,是否是哪一个妹子都OK?

我参与设计过一套帐务系统,把全部帐户间的转帐操做所有抽象为这样一个接口,它所表达的业务含义是:从帐户from向帐户to转入金额amount元,记帐科目是type:

public interface AccountService{
    public void trans(Account from, Account to, Money amount, TransType type);
}

在这个接口的“掩护”下,咱们更换过不少种底层实现方式:单边帐、双边帐、会计科目记帐;同步操做、异步操做、批量操做;等等等等。没有一次变动影响到了接口调用方,最终找到了既能知足全部业务功能、又提升了处理性能的最佳方案。这就是在好的抽象设计下的代码可替代性带来的好处。

也有反面例子。我参与设计过一套Java操做Excel文件的工具,底层用的是POI组件。这套工具的核心接口大概是这个样子的:

public interface ExcelService<T>{
    public List<T> read(HSSFWorkbook workBook);
}

这个接口的功能,简单来讲就是传入一个Excel文件、并把其中的数据解析为对象T。它的主要问题在于:底层实现——也就是HSSFWorkbook——被暴露出来了。这就致使了这个接口只能解析2003版的Excel文件,面对用户上传的2007版Excel文件,它就无能为力了。并且,若是要把工具升级到2007版,全部调用方都必须跟着一块儿改:在咱们的系统里,这意味着要多修改二十多处代码、多回归测试几十个功能。其中的困难可想而知。

若是这个接口设计得更好,它的底层代码的可替代性就更高,重构、优化、需求变动时须要修改的地方就更少。改得越少,开发的工做量、加班量就越少,出bug的概率也会更少。

可扩展性

为何说抽象设计得好,代码的可扩展性就越好呢?这和可替代性有类似之处:根子上仍是由于一个好的抽象设计能隐藏它的底层实现。就像家里给小孩儿炖汤;妈妈去厨房尝了一勺,而后多撒了一把葱花;姥姥又去尝了一勺,而后多加了点姜片;奶奶又去尝了一勺,而后多加了点花椒……(最后留给小孩儿的就只剩一勺浓汤宝了哈哈)。

咱们有一个查银行卡列表的接口,客户端查到列表后,须要根据不一样的场景来展现或“置灰”某些卡。例如,划扣场景下,不支持自动划扣的卡就必须置灰;解绑定场景下, 跟某些业务绑定的卡就必须置灰;业务绑卡场景下,已经跟该业务绑定的卡就必须置灰……等等等等。

咱们为这个业务所设计的抽象是这样子的:

public interface CardListService{
    List<Card> query(long userId, Scene scene);}
//核心实现是这样的
public class CardListServiceImpl{
    private Map<Scene, CardListService> serviceMap;
    public List<Card> query(long userId, Scene scene){
        return serviceMap.get(scene).query(userId, scene);
    }
}
// 返回字段是这样的
public class Card{
    // 客户端根据这个字段的值来判断当前银行卡是展现仍是置灰
    private boolean enabled;
    // 其它卡号、银行名等字段,和accessor略去
}
// 入参是这样的
public enum Scene{
     DEDUCT,
    UN_BIND,
    BIND;
}

客户端不须要关注List&lt;Card&gt;中的银行卡是否是支持自动划扣、是否是和某个业务绑定,只须要根据返回结果中的enabled字段来展现或置灰便可。由服务端来根据客户端传入的Scene来判断这些卡是否应当展现。并且,不管哪一个Scene下要增长逻辑,或者要增长新的Scene,都只须要服务端作出修改,客户端是不须要变的。并且即便是服务端,须要修改或增长的代码量也不大,很是简单。

简单易用、可替代和可扩展这些,对于业务系统的重要性有时甚至比对技术中间件还要高。业务系统的一个重要特色,就是业务需求在不停变化、频繁变化:今天需求是这样,明天就推翻不作了,后天又从新提出来,大后天再改一版……若是系统的设计实现被需求牵着鼻子走,那开发就有改不完的代码、加不完的班了。好好地设计一套业务抽象,让系统和代码简单易用、易于替换、易于扩展,才有可能在少修改代码、甚至不修改代码的基础上去知足多变的业务需求。开发才能从业务代码中释放出来,去提高本身、优化系统。

不加班、不秃头<br/>

HOW

怎样设计一个好的抽象呢?其实咱们已经有不少方法论/工具箱了:高内聚/低耦合、封装/继承/多态、SOLID、设计模式……等等等等,不一而足。只不过之前讨论它们的时候,更多地是在“就事论事”地讨论它们自身,而并无考虑到它们与“抽象”的关系。怎样从业务抽象的角度去理解和应用这些方法和工具、又怎样运用它们来创建良好的业务抽象呢?下回分解吧。

相关文章
相关标签/搜索