(转载)提高系统OOP抽象以应对复杂的需求
提高系统OOP抽象以应对复杂的需求, 转自:http://www.nowamagic.net/librarys/veda/detail/1373
有人问我如何构建一个比较好的类阶层次,如何使用面向对象进行设计,或者问为什么我看了那么多面向对象和设计模式的书一到使用的时候却总是写出面向过程的代码。每当我碰到这些问题的时候我总是回答,其实我也不知道。真的,其实我也不知道。
虽然我总是张口闭口面向对象,总是看到一个问题后就谈这个有点XXX模式的影子,但大部分时候碰到一个问题我还是一片空白,不知道如何去分析设计和实现出好的面向对象。所以,我只想谈谈我是如何实践面向对象的,这对我自己有用但不一定对你有用。嗯,回到正题。
回顾编程方法的发展史,我想不外乎两个字:抽象。
从最早的汇编语言中使用的子例程到结构化编程,然后到面向对象、面向组件以及面向服务。我觉得都是不断地提升抽象的层次。所以编程方法没有好坏,只有适合不适合。在汇编时代问题规模都很小,所以我们需要的抽象能力不需要太强。而现代的软件项目,问题的规模非常庞大,需要考虑的事情非常多(虽然纯粹的技术含量不一定有汇编时代的高),我们就必须使用抽象层次更高的方法来匹配我们的问题规模。
面向对象编程方法的出现也不外乎如此,所以我们在使用面向对象方法开发的时候一个目的就是要提升抽象层次(比如现在由有人提出面向对象已经不足以匹配并行软计算的抽象层次,所以不再教授面向对象,转而教授函数编程)。
而我觉得提升抽象层次的一个好方法就是用代码与人交谈,用代码来表达你的思想,在代码中形成一个个"概念",或者说代码就是用来传递知识的。我将概念二字加上引号并加粗是有特别强调的意思,这个在后文我会谈谈什么是这里所说的概念。我不想在表面文字上谈论太多,我们来实践吧。
注意,本文代码仅仅为了说明一些问题或现象,并不考虑业务上的合理性,读者可以自行分辨然后拿自己的业务代码进行思考。
方法的参数
不知道你写过或见过下面的代码没有:
bool IsValid(string userName, string password, string email, int status);
如果你见过然后还放任不管,那么你就丧失了一次提升抽象层次的机会。Robert.Martin在《Clean Code》里谈到,方法的参数不宜过多,如果有过多的参数我们就要特别审视一番。当我们审视上面的方法参数时,我们发现其实这些参数都应该属于同一个东西,而现在我们没有。类似字符串、整型这些类型,抽象层次太低了,没有任何的领域含义。而且我们发现,上面方法的参数和方法名字IsValid也不在同一个抽象的层次上,我们阅读到IsValid这个方法时,我们甚至不能一下子了解其目的:哦,这个方法是检查用户名、密码、邮件以及状态的有效性么?哎,多么啰嗦,还不一定对。如果我们多看两眼这些参数我们或许会写出这样的代码:
bool IsValid(User user); public class User
{
public string UserName{get;set;} public string Password{get;set;} public string Email{get;set;} //这里仅仅为了方便使用整型代替枚举,其实可以新建一个枚举来提升这里的抽象层次-_-
public int Status{get;set;}
}
哦,看到这个方法后我就知道这个方法是用来检查用户的合法性的,除此之外我们还创建了一个概念"用户",我们将一堆零散的数据聚合成一个新对象,向你传递了一个知识。
不要迷恋哥
然后我们再进入到IsValid方法内部看看:
bool IsValid(User user)
{
if(user.UserName.Length > 0 && user.Email.Contains("@")){
//....
}
//...
}
我们发现这个方法内部干的事儿就是不断的询问User对象,探寻User对象的内部状态然后做出一些判断。探寻别人的隐私是不好的,这么强的依赖别人的内部状态违反了面向对象封装的原则。如果我们的一段代码总是不断的探寻另外一个对象的内部状态,然后做出一些判断,我们就应该思索:这个被询问的对象是不是缺少一个概念?或者说这段代码应该属于被询问的那个对象而不是现在的这个对象:
public class User
{
public bool IsValid()
{
if(userName.Length > 0 && email.Contains("@"))
{
//....
}
//...
}
}
这里抱成一团
有的时候我们发现,方法内部的某部分代码围绕着一个中心点在纠结。跟方法内的其他代码有些间隙,最重要的是这段代码严重的影响了整个方法的可读性,因为有了这段代码,方法体变长,方法更难读懂。这个时候我们应该对方法内部的代码排排序,检查一下这些代码,是不是有的代码是为了干一件事儿(方法刚写的时候可能是这样分的,为了同一个目的的代码都放到一块儿,但随着时间流逝,新代码不断的加入可能违反了这个原则)。比如上面的IsValid方法或许如下面这样实现:
public class User
{
public bool IsValid()
{
if(userName.Length > 0 && (email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...)))
{
//....
}
//...
}
}
那一长串&&和||不就是为了验证Email的合法性么?因为它的存在搞得这里一团糟,如果我们能进一步提升抽象层次:将一团代码提取到一个方法,用方法名来描述方法本身:
public class User
{
public bool IsValid()
{
if(userName.Length > 0 && IsEmailValid())
{
//....
}
//...
} bool IsEmailValid()
{
return email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...));
}
}
不仅这团代码的抽象层次提高了,IsValid顿时也高贵起来了,好懂多了(不过那团代码依然存在,不过隐藏在抽象的背后,忘记是不是有这么一个名言:每个漂亮的接口后面都有一个肮脏的实现[玩笑话])。
人格分裂
碰到过这样的代码没有,一个类里五个方法,三个方法访问a,b,c属性,另外两个方法访问d,e,f属性。好像有一条隐约可见的分界线将这个类一分为二。有的时候这条分界线并不十分明显,可能还有一些交叉。这实际上和上面说的提取一个方法类似。我们只是比方法更高一个层次:缺少一个类型将这部分代码独立出去。还是看上面的User类,我们发现有那么几个方法总是围绕着email在打转,对User类其他的东西倒不是很关心:
public class User
{
public bool IsValid()
{
if(userName.Length > 0 && IsEmailValid())
{
//....
}
//...
} bool IsEmailValid()
{
return email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...));
} public string EmailAddress()
{
return string.Format("\"{0}\"<1>",userName,email);
} private string Convert()
{
if(email.IndexOf('#') != -1)
{
return email.Replace('#','@');
}
}
}
我想或许我们缺少一个Email的概念,这样就可以将这几个方法以及其要使用的属性封装起来:
public class User
{
private Email email; public bool IsValid()
{
if(userName.Length > 0 && email.IsEmailValid())
{
//....
}
//...
} } public class Email
{
private string address; bool IsEmailValid()
{
return address.Contains("@") && (address.EndsWith(".com") || address.EndsWith(".biz")...));
} public string EmailAddress(string userName)
{
return string.Format("\"{0}\"<1>",userName,email);
} private string Convert()
{
if(email.IndexOf('#') != -1)
{
return address.Replace('#','@');
}
}
}
好了,就谈这么多吧。类似的提升抽象层次的例子还有很多很多,无非就是通过重组织代码,形成一些概念,向阅读代码的人传递领域的知识。然后我想说的是,面向对象设计或许真的很难,需要丰富的经验,但是面向对象编程并不难,只需要我们有一颗精益求精的心就可以了。代码不是写出来然后就放到那里,然后就不去管了,我们需要时不时的去照顾我们的代码,然后观察,然后不断的去雕刻。
面向对象很像路边摊的大妈摊鸡蛋饼。不断的把饼摊薄,饼的每个小块都很薄,然后就很容易熟。
(转载)提高系统OOP抽象以应对复杂的需求的更多相关文章
- 用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)
1.概述 在Java中,我们一般通过集成Thread类和实现Runnnable接口,调用线程的start()方法实现线程的启动.但如果并发的数量很多,而且每个线程都是执行很短的时间便结束了,那样频繁的 ...
- (转载)提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...
- 【转载】图说OOP基础(一)
本文用图形化的形式描述OOP的相关知识.对OOP进行系统化的梳理,以便掌握. 涉及知识点: OOP的相关知识 OOP知识[Object-Orientation Programming 面向对象编程]总 ...
- 转载--提高C++性能的编程技术
读书笔记:提高C++性能的编程技术 第1章 跟踪范例 1.1 关注点 本章引入的实际问题为:定义一个简单的Trace类,将当前函数名输出到日志文件中.Trace对象会带来一定的开销,因此在默认情况 ...
- [转载]提高rails new时bundle install运行速度
最近在新建rails项目时,rails new老是卡在bundle install那里,少则五分钟,多则几十分.这是因为rails new时自动会运行bundle install,而bundle in ...
- 【转载】系统吞吐量(TPS)、用户并发量、性能测试概念和公式
系统吞度量要素 一个系统的吞度量(承压能力)与request对CPU的消耗.外部接口.IO等等紧密关联.单个reqeust 对CPU消耗越高,外部系统接口.IO影响速度越慢,系统吞吐能力越低,反之越高 ...
- [转载]Android系统开机画面的实现
Android系统开机画面分为下面三个阶段: 1.开机图片:Android内核是基于标准内核的,对linux比较熟悉,特别是在开发板上移植过Linux系统的人就知道在内核引导过程中会显 示出一 个小企 ...
- SQL Server实现读写分离提高系统并发
转自:http://www.canway.net/Lists/CanwayOriginalArticels/DispForm.aspx?ID=666 在一些大型的网站或者应用中,单台的SQL Serv ...
- [转载]Windows系统的错误报告保存在哪个文件夹里?
转自:http://www.xitonghe.com/jiaocheng/xp-786.html Windows系统的错误报告保存在哪个文件夹里? 发布时间:2014-10-31 20:52:20 ...
随机推荐
- bash shell中测试命令
bash shell中测试命令 test命令提供了if-than语句中测试不同条件的途径.如果test命令中列出的条件成立,test命令就会退出并返回退出状态吗0 .这样if-than语句就与其他编程 ...
- Linux Namespaces机制
转自:http://www.cnblogs.com/lisperl/archive/2012/05/03/2480316.html Linux Namespaces机制提供一种资源隔离方案.PID,I ...
- 细说"回车"和"换行"的故事
引言 最近在php还有c#以及memcache的shell当中经常看到\r\n的写法,刚开始还没注意, 不过后面感觉这样写有些不对头,\r表示回车 \n表示换行,那这样不是换行了两次吗? 为了解决疑 ...
- 安装lnmp集成环境
具体配置看原文,不重新复述: 原文:https://lnmp.org/install.html 因为配置数据库主从,需要保持两台mysql数据库服务器的mysql版本号一致,所以又重新装了一次..重新 ...
- javascript痛点之一变量作用域
1.用var声明的变量是有作用域的,比如我们在函数中用var声明一个变量 1 'use strict'; 2 function num(){ 3 //用var声明一个变量num1 4 var num1 ...
- {网络编程}和{多线程}应用:基于TCP协议【实现多个客户端发送文件给一个服务器端】--练习
要求: 实现多个客户端发送文件给一个服务器端 提示:多个人创建客户端发送文件,服务端循环接收socket,从socket中获取文件 说明:这里我们只要建立一个服务端就可以了,然后让多台电脑使用客户端给 ...
- javaSE_07Java中类和对象-封装特性
一.谈谈什么是面向对象的思维 理解面向对象,重点是要思考以下的问题 面向过程 vs 面向对象 Ø 谈谈什么是面向过程的编程思想? Ø 为什么有面向过程还要有面向对象? Ø 谈谈什么是面向对象的编程思想 ...
- 【锋利的jQuery】表单验证插件踩坑
和前几篇博文提到的一样,由于版本原因,[锋利的jQuery]表单验证插件部分又出现照着敲不出效果的情况. 书中的使用方法: 1. 引入jquery源文件, 2. 引入表单验证插件js文件, 3. 在f ...
- datatables 学习笔记1 基础篇
本文共3部分:基本使用|遇到的问题|属性表 1.DataTables的默认配置 $(document).ready(function() { $('#example').dataTable(); } ...
- Nginx实用教程(二):配置文件入门
Nginx配置文件结构 nginx配置文件由指令(directive)组成,指令分为两种形式,简单指令和区块指令. 一条简单指令由指令名.参数和结尾的分号(;)组成,例如: listen backlo ...