面向对象设计之五大原则
2017-06-08 21:29
253 查看
本文目录 :
五大原则总览单一职责原则
接口隔离原则
开放-封闭原则
里氏替换原则
依赖倒置原则
本文小结
参考文档
在按照我的理解方式审查了软件开发的周期后,我得出一个结论:实际上满足工程设计标准的唯一软件文档,就是源代码清单。
-- Jack Reeves (他发布了「什么是软件设计?」的开创性论文)
1、 五大原则总览
面向对象是一种高度抽象的思维,所有的软件设计几乎都是围绕着类(模块)来展开的,面向对象的设计基本就是在探讨类与类之间的关系。面向对象设计的五大原则是:单一职责原则、接口隔离原则、开放-封闭原则、替换原则、依赖倒置原则。它们是软件中的常用的设计模式的基础。
为什么需要这些原则呢?因为代码容易腐化(借用参考书2中的说法),不断的需求变化和新增功能,导致软件中的糟糕代码不断累积,导致腐化,变得难以维护和拓展,这些设计原则能有效防止软件腐化。
因为这些原则听起来比较抽象,我借用通篇的例子来讲一下我对这些原则的理解。
本文以 发布博客 这一行为过程,设计符合上述五个原则的例子(借鉴参考文档1的例子)。这个例子按照五个原则拆成5个小例子。因为几个小例子之间有可能有多于一种原则的情况,我就只着重讲要讲的内容,毕竟五个原则其实某种程度上也是有点交叉的。
例子所用的语言是PHP,我们就不谈PHP面向对象中的一些缺陷了,但是设计原则是一种指导,无碍实现。
先发表一下我的设计感受:期待变化。
2、 单一职责原则
单一设计原则:Single Responsibility Principle,即SRP。这个原则讲的是减少类之间的耦合、提高类的复用性,正所谓职责单一。分析一下需求(看,需求来了),发布博客这一行为,涉及到的东西有哪些,罗列一下:
1.要发表的博客(博客实体)
2.博客操作(发布、删除、浏览等,虽然需求只是发布,但是要期待变化呀)
3.博客处理逻辑(发布的时候要进行的逻辑处理,或其他操作处理)
4.面向用户的接口(耳熟能详的控制器)
思考一下上面的区分是否职责够单一,好了,写一下上面的设计的代码流程,如下:
$blog = new Blog(); //博文实体 $blog->post_time = time(); $blog->author = '匿名者'; $blog->title = '面向对象示例'; $blog->content = '如你所见,这是示例,谢谢赏读,不吝指正'; $model = new FileBlogModel; //基本操作模型 $model->setConfig('E:\test'); $proc = new BlogProcModel; //逻辑处理模型 $controller = new BlogController; //创建一个控制器,面向用户,应用开发者 //发布博文 $controller->release($proc, $model, $blog); //依赖注入 //浏览博文 $controller->view($proc, $model, $blog); //删除博文 $controller->delete($proc, $model, $blog);
接着,我们来逐一实现上面的具体代码。
3、 接口隔离原则
接口隔离原则:Interface Segregation Principle,即ISP。接口做到专一,不依赖不需要的接口方法,做到“隔离”。博客操作模型中,设计它的接口时,一个接口就只有一个操作,做到专一。比如说,你不应该在 write 接口的里边有 close 操作,虽然大多数时候是写完即关,但是你怎么去实现那些要write多次的情况呢?重新写个接口?所以要做到隔离,不加入多余的操作。这就是你在期待变化的时候做出了衡量。
/** * 博文操作模型抽象 */ abstract class BlogModel { public function open() { return true; } public function close() { return true; } abstract function read($author, $title); abstract function write($content, $post_time, $author, $title); abstract function delete($author, $title); }
4、 开放-封闭原则
开放-封闭原则:Open-Close Principle,即OCP。一个模块,扩展性上是开放的,更改性上是封闭的。刚刚写了个博文操作的抽象类,为什么要写成抽象的呢?因为要支持扩展性和封闭性呀,利用对象的继承性和多态性进行扩展,继承的类在固定的抽象上的修改是封闭的,扩展性就是在期待变化呀。
比如我们可以写个用文件方式操作博文的类
FileBlogModel,它继承
BlogModel。如下:
可是适当增加一些固有的操作,修改也时封闭的。
/** * 文件博文存储实现类 */ class FileBlogModel extends BlogModel { private $rootpath; public function setConfig($rootpath) { $this->rootpath = $rootpath; } public function open() { if (!file_exists($this->rootpath) || !is_dir($this->rootpath)) { exit("rootpath: {$this->rootpath} is not exist!");//应该抛出一个异常 } return true; } public function read($author, $title) { $data = file_get_contents($this->getFilePath($author, $title)); return json_decode($data, true); } public function write($content, $post_time, $author, $title) { $data = json_encode([ 'content' => $content, 'post_time' => $post_time, 'title' => $title, 'author' => $author ], JSON_UNESCAPED_UNICODE); return file_put_contents($this->getFilePath($author, $title), $data); } public function delete($author, $title) { return unlink($this->getFilePath($author, $title)); } private function getFilePath($author, $title) { $is_win = strpos(PHP_OS, 'WIN') === false; $dirpath = $this->rootpath . DIRECTORY_SEPARATOR . $author; $path = $is_win ? $dirpath : iconv("UTF-8","gb2312", $dirpath); !file_exists($path) && mkdir($path); $filepath = $dirpath . DIRECTORY_SEPARATOR . $title; return $is_win ? $filepath : iconv("UTF-8","gb2312", $filepath); } }
又比如我们可以扩展写个 Mysql 数据库操作博文的类
MysqlBlogModel,也是继承
BlogModel,如下:
/** * mysql数据库博文存储实现类 */ class MysqlBlogModel extends BlogModel { private $link; //mysql资源句柄 private $config = []; public function setConfig($host, $user, $password, $database) { $this->config = [ 'host' => $host, 'user' => $user, 'pass' => $password, 'db' => $database, ]; } public function open() { $c = &$this->config; $this->link = mysqli_connect($c['host'], $c['user'], $c['pass'], $c['db']); return empty($this->link); } public function close() { return mysqli_close($this->link); } public function read($author, $title) { $sql = "SELECT title,content,post_time,author FROM blog" . " WHERE author=$author AND title=$title"; $result = mysqli_query($this->link, $sql); $row = mysqli_fetch_assoc($result); mysqli_free_result($result); return $row; } public function write($content, $post_time, $author, $title) { $sql = "REPLACE INTO blog(title,content,post_time,author)" . " VALUES($title,$content,$post_time,$author)" . " FROM WHERE author=$author AND title=$title"; return mysqli_query($this->link, $sql); } public function delete($author, $title) { $sql = "DELETT FROM blog WHERE author=$author AND title=$title"; return mysqli_query($this->link, $sql); } }
5、 里氏替换原则
里氏替换原则:Liskov Substitution Principle,即LSP。里氏指Liskov女士。该原则指出:子类必须能替换它们的父类,并出现在父类能够出现的任何地方。下面写的
BlogProcModel逻辑处理类,就能体现这一点,如
save(BlogModel $model, Blog $blog)方法,入参
BlogModel是个抽象的父类,不管你后面传入的是
FileBlogModel,还是
MysqlBlogModel,它们的
open(), write(), close()一样使用,并且能根据你传入什么子类,就执行子类相应的方法,这就是类多态的体现了。
/** * 博文处理模型-逻辑处理 */ class BlogProcModel { public function save(BlogModel $model, Blog $blog) { $content = $blog->content; $content = $this->escape($content); /* 转义非法字符 */ //$content = $this->filter();/* 过滤非法字符 */ //$content = $this->checkSensitiveWord($content);/* 检查敏感词 */ //$content = $this->checkCopy($content);/* 博文抄袭检测 */ $model->open(); $return = $model->write($content, $blog->post_time, $blog->author, $blog->title); $model->close(); return $return; } private function escape($content) { return addslashes($content); } public function read(BlogModel $model, Blog $blog) { $model->open(); $data = $model->read($blog->author, $blog->title); $model->close(); return $data; } public function delete(BlogModel $model, Blog $blog) { $model->open(); $return = $model->delete($blog->author, $blog->title); $model->close(); return $return; } }
6、 依赖倒置原则
依赖倒置原则:Dependence Inversion Principle,即DIP。就是依赖关系倒置为依赖接口,这里的接口是对问题的抽象。即抽象不能依赖于具体,具体应该依赖于抽象。依赖抽象是实现代码扩展和运行期内多态的基础。比如下面的控制器
BlogController的方法
release(BlogProcModel $proc, BlogModel $model, Blog $blog),它依赖于入口参数 BlogProcModel 、BlogModel。很多人想,为什么要写成参数形式?不在方法内写死?其实用上 写死 这个字眼你也就知道弊端了。这个方法依赖于不写死的入参,而且是抽象!!(
BlogProcModel你可以更进一步抽象。)
我们只需在外边具体化抽象接口或类,也可运行时传入具体化参数。 这里到处都期待着变化。
/** * 博客控制器 */ class BlogController { public function release(BlogProcModel $proc, BlogModel $model, Blog $blog) { $return = $proc->save($model, $blog) ? 'ok' : 'false'; echo "发表 $return!<br/>"; //view } public function view(BlogProcModel $proc, BlogModel $model, Blog $blog) { $data = $proc->read($model, $blog); echo "<br/>标题: {$data['title']}<br/>" . "作者: {$data['author']}<br/>" . "发表时间: ".date('Y年m月d日', $data['post_time'])."<br/>" . "正文内容: {$data['content']}<br/><br/>"; //view } public function delete(BlogProcModel $proc, BlogModel $model, Blog $blog) { $return = $proc->delete($model, $blog) ? 'ok' : 'false'; echo "删除 $return !<br/>"; //view } }
小结
把整个例子串起来看,会发现许多原则分布各处,如接口隔离原则的小例子用单子职责原则也能说得通,依赖倒置的小例子用里氏替换原则也说得通,等等。因为客户的需求是不稳定的,也是软件实现中最不稳定的因素,所以我们要设计与之相容的代码,设计中要本着期待变化的心情去设计。
实际编码的时候,其实框架如 Thinkphp5.0 就已经按设计原则进行比较好的设计,我们只是在一个规范的框架内开发而言,可以感受到需求变更时,框架带来的便利和可维护性。那为什么还要懂得设计原则呢?这是一种内功修养吧。万一后面你不得不造轮子呢?
主要参考文档:
1、《PHP核心技术与最佳实践》 第2章 面向对象的设计原则
2、《敏捷软件开发:原则、模式与实践》 第二部分 敏捷设计
-end-
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证) 发表日期:2017年5月30日
相关文章推荐
- 面向对象五大设计原则
- 面向对象设计的五大原则
- 面向对象的五大设计原则
- 面向对象设计的五大原则
- 面向对象设计的五大原则(注:转载的)
- 面向对象设计五大原则SOLID
- 面向对象类设计的五大原则(一)单一职责原则Single Responsibility Principle
- 面向对象思想所遵循的五大设计原则
- 面向对象设计的五大原则
- 面向对象设计五大原则
- 面向对象设计的原则
- 面向对象的设计原则-类设计原则
- 面向对象设计5大原则
- 面向对象设计原则
- 面向对象类设计的一些原则
- 面向对象的设计原则-类设计原则
- 面向对象设计模式与原则
- 面向对象的设计原则
- 面向对象设计原则1 把所有数据封装成类的私有成员
- C#面向对象设计模式纵横谈(1):面向对象设计模式与原则