编程思想之原则

本文探讨了编程中的重要原则,如层次化、不重复、关注点分离等,并介绍了设计模式的六大基本原则,帮助开发者理解良好的软件架构设计。

一、一个艺术的问题

先来欣赏两幅名画,一副是西方的油画《最后的晚餐》,一副是中国画《清明上河图》。比较两幅画的特点,一个是透视投影(Perspective Projection),一个是正交投影(Orthographic Projection)。这使我们非常容易的联想到了两款知名度很高的游戏《和平精英》和《王者荣耀》。我们知道这两款游戏一个是3D的,一个是2.5D(2D场景+3D人物+3D特效)的,在此可以体会一下下面两幅图和这两款游戏的感觉是不是很像。

简单介绍一下OpenGL:投影矩阵会创建一个视景体对物体坐标进行裁剪,得到的裁剪坐标再经过透视除法之后,就会得到归一化设备坐标。归一化设备坐标再经过视口转换,最终将坐标映射到了屏幕上。不管是正交投影还是透视投影,最终都是将视景体内的物体投影在近平面上,这也是 3D 坐标转换到 2D 坐标的关键一步

正交投影(Orthographic Projection)

/**

* Computes an orthographic projection matrix.

*

* @param m returns the result 正交投影矩阵

* @param mOffset 偏移量,默认为 0 ,不偏移

* @param left 左平面距离

* @param right 右平面距离

* @param bottom 下平面距离

* @param top 上平面距离

* @param near 近平面距离

* @param far 远平面距离

*/

public static void orthoM(float[] m, int mOffset,float left, float right, float bottom, float top,

float near, float far)

透视投影(Perspective Projection)

/**

* Defines a projection matrix in terms of six clip planes.

*

* @param m 透视投影矩阵

* @param offset 偏移量

* @param left

* @param right

* @param bottom

* @param top

* @param near

* @param far

*/

public static void frustumM(float[] m, int offset,float left, float right, float bottom, float top,

float near, float far)

从前画家通过作画的形式来完成艺术创作,如今程序员通过写代码的形式完成艺术创作。写出的代码就是艺术品,实现的产品也是艺术品。写代码和搬砖绝对是不一样的,一天能搬多少砖是可以定量的,一天能写多少代码是无法定量的。如果你看到某个程序员在那里发呆、抽烟、刷手机等,请不要奇怪,他是在等灵感的出现,没错和画家音乐家一样,程序员也是需要灵感的。几十年来无数天才程序员的灵感形成了众多的编程原则、范式、模式,而这些都是充满智慧的艺术结晶。

二、编程原则(Principles)

1、Indirection (Layering)

间接/层次化原则。

Applications:系统应用层,这一层中都是我们使用手机时都会直接接触到的各种应用。

Application Framework:Java 接口框架层,这一层是为了上层应用提供各种接口。

Native C/C++ Libraries && Android Runtime:分别是原生 C/C++ 库,安卓运行时环境。这一层中,C/C++ 库集成了许多诸如 OpenGL ES 这样的开源库,提供了很多封装好的方法。而运行时环境则是与一些核心库、Dalvik Virtual Machine 相关的东西。

HAL(Hardware Abstract Layer):硬件抽象层,它主要是在 Framework 层和 Linux Kernel 层之间起到一个链接作用。

Linux Kernel:即 Linux 内核层,整个 Android 系统实际上是基于 Linux 的内核搭建起来的。

以上是一个android五层结构的大致介绍,从这个例子上可以比较清楚的看出Layering层次化的概念,每一个层各司其职。这样的分层除了看起来简单明了外还有什么好处呢?这就要讲层次化原则内涵的Indirection间接性的作用了。Android基于Linux内核实现,Linux是GPL许可,即对源码的修改都必须开源,而Android是ASL许可,即可以随意使用源码,无需开源。如果硬件厂商的驱动写在kernel,那代码就要开源,这涉及到硬件厂家的机密利益,所以很多硬件厂商是不愿意这么干的。为了解决这个问题,在android用户空间开辟一个HAL硬件抽象层,遵守ASL的许可,如果硬件驱动开源的写在Kernel里,Framework直接调用,而不愿意开源的就写在HAL层里,间接(Indirection)调用,实现闭源。

HAL将硬件抽象化,实际实现的时候就是三个结构体的继承实现(C语言的OOP思想),这使我们想起了平时开发时常常念叨的抽象层的概念。比如经典的MVC框架,我要从DB中读取数据,装入model,并在controller控制下展现到view中。如果在调用者(Model)和被调者(DB)中间定义出各种抽象类和接口,model直接调用这些抽象类和接口,而不是具体的实现类,那这就相当于加入model层和DB层加入了indirection,这些抽象类和接口就是抽象层。

除了android的五层架构、MVC等框架是我们常见的能体现层次化编程的原则外,还有ISO/OSI七层模型等。层次化就是把系统分成不同的层次,严格规定层次间的调用关系,极其有利于大型项目的开发。

拓展举例《自动驾驶研发中的软件架构问题》

https://mp.weixin.qq.com/s/5WMvrWeD7RtxhZyIgA6sZA

2、DRY (Don't Repeat Yourself)

不重复原则。

当有两段代码相同或相似时,就要考虑DRY,就是不重复原则。面向过程的编程(POP)就体现了这个原则:功能模块化,相同的功能或过程作为一个模块,只用实现一次就可以多次使用。模块化、组件化是这一原则的体现。

需要注意的是模块化和层次化是不一样的,模块化有可能和层次化同步,也有可能跨层次。比如我们把文件File的创建、删除、重命名、移动等功能在一个util类中做模块化处理,提供静态的全局调用,那么项目中所有层次的代码都可以使用。再比如,MVC的model层故名思意就是模块化的层,主要是根据业务逻辑分类模块化,如果从业务逻辑关系的角度来看,这些被分出来的模块可能属于不同的业务层次,但是从整个架构来看,就是属于model层。

3、SoC (Separation of Concerns)

关注点分离原则。

将复杂系统分治成不同的子系统,尽可能将变化控制在子系统中,每一个子系统只关主一个点。关注点分离原则期望每个子系统都有排他性和目的单一性。也就是说,任何子系统都不应分担另一个子系统的责任,也不应包含不相关的责任。典型的例子就是网站html/css分离;还有结构化编程(SP).h和.cpp分离;还有就是android系统的mvc架构。

4、IoC (Inversion of Control)

控制反转原则。Don't call me, I'll call you。

最常见的方式叫做依赖注入(Dependency Injection,简称DI)

在IoC出现以前,组件之间的协调关系是由程序内部代码来控制的,或者说,以前我们使用New关键字来实现两组间之间的依赖关系的。这种方式就造成了组件之间的互相耦合。IoC(控制反转)就是来解决这个问题的,它将实现组件间的关系从程序内部提到外部容器来管理。也就是说,由容器在运行期将组件间的某种依赖关系动态的注入组件中。

A、类和类之间关系

(1)is a:继承

(2)like a:实现

(3)has a:组合,聚合,关联

关联:

public class Car{}

public class User{

private Car car;

public void setCar(Car car){

this.car=car;

}

}

聚合:

public class Engine{}

public class Car{

private Engine engine;

public Car(Engine engine){

this.engine=engine;

}

}

组合:

public class Brain{}

public class Person{

private Brain brain;

public Person(){

this.brain=new Brain();

}

}

(4)use a:低耦合依赖

public class Car{

public void move(){}

}

public class User{

public void move(Car car){

car.move();

}

}

5、CoC (Convention over Configuration)

约定优先原则。

A、java命名规范

1、项目名全部小写

2、包名全部小写

3、类名首字母大写,如果类名由多个单词组成,每个单词的首字母都要大写。

如:public class MyFirstClass{}

4、变量名、方法名首字母小写,如果名称由多个单词组成,每个单词的首字母都要大写,就是所谓的驼峰式命名法camelCase。

如: int index=0;

public void toString(){}

5、常量名全部大写

如:public static final String GAME_COLOR=”RED”;

6、所有命名规则必须遵循以下规则:

1)、名称只能由字母、数字、下划线、$符号组成

2)、不能以数字开头

3)、名称不能使用JAVA中的关键字。

4)、坚决不允许出现中文及拼音命名。

B、google aosp规范

公开静态字段(常量)为全部大写并用下划线连接 。

非公开且非静态字段的名称以 m 开头。

静态字段的名称以 s 开头。

其他字段以小写字母开头。

C、Mybatis

Mybatis是javaweb开发中作为替代hibernate的新一代持久层框架。它有一个重要功能,就是把数据库定义好的表自动映射生成javabean。试想一个项目的数据库表有几十上百个,每个表又有几十上百个字段,这时domain层代码量会极大,而Mybatis可以帮助我们轻松的解决这个问题,它可以根据定义好的数据库自动生成javabean,包括注释,甚至基本的增删改查也会自动生成。之所以能够实现代码自动生成,重要原因就是惯例原则,把数据库表向javabean映射并添加相应的domain层代码就是早就形成的惯例,甚至达到了无脑必加的操作了。

这里有个问题,数据库字段的命名规则和java的命名规则是否一致。一般来讲一个字段就有一个单词没有问题,那多个单词就分两种情况了:全小写下划线分割、驼峰试。而Mybatis可以提供下划线式向驼峰式自动转换,目前多数情况下都是推荐驼峰式命名。当然了,数据库里字段命名加前缀m、s就彻底没意义了。

D、DataBinding向struts的靠近之路

如下有个javabean,如何把一个从数据库初始化的携带数据的User对象映射到View层?这是无论web端还是客户端都会遇到的问题。

早期的web端有个EL表达式:${},这是家喻户晓的,在jsp时代bean向view映射的代码是这样的:

<jsp:user id="user" class="com.test.User" scope="page"/>

<input name="name" type="text" value="${user.name}">

如今DataBinding也有个链接表达式:@{},可以说这完全是模仿EL表达式的:

<layout>

<data><variable name="user" type="com.test.User" /></data>

<TextView android:text="@{user.name}" />

</layout>

再来看一下struts实现的更简洁:

<input name="User.name" type="text" value="">

到此我们发现把携带数据的bean向view映射的过程基本就是一个惯例,而且操作步骤、过程都有相似性,而DataBinding和Struts的设计思路就是简化这个约定俗成的过程。

进一步总结,我们所有的应用软件的开发都离不开“获取数据-数据显示”的基本范畴,这是最大的约定,所以也是最优先要思考解决的宏观性的问题。于是我们从需求分析开始,就要思考这个问题:能不能需求定下来数据库(或者数据结构)就定义完成,数据库一旦定义完成就能通过字段定义的一致性惯例实现自动向view层映射,这样就能做到需求定下来,功能基本就实现了呢?这是一个比较大的课题,rails的出现就是和这个课题有重大联系的,rails的具体思想是:目录结构我来定义,你只要在我定义好的目录中放东西就可以了。

 

设计模式六大原则

1.单一职责原则

单一职责原则英文全称:Single Responsibility Principle 简称SRP

定义:就一个类而言,应该仅有一个引起它变化的原因。简单来说,其实就是一个类只负责一个工作。就和流水线上的员工一样,每个人只负责一个工作。

注意:单一职责的划分界限并不是总是那么清晰,很多时候都是需要靠个人经验来界定的。

2.开闭原则

开闭原则英文全称:Open Close Principle, 缩写:OCP

定义:软件中的对象(类,模板,函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开放–关闭原则。其实就是如果更改需求尽量做到不修改之前代码。这样会避免原有模块的出错,当需求更改时尽量去扩展而不是去修改。

3.里氏替换原则

里氏替换原则英文全称:Liskov Substitution Principle,缩写:LSP

定义:所有引用类的地方必须能透明地使用其子类的对象。其实就是依赖面向对象的继承和多态特性,将接口参数变成父类,只要实参是其子类就可以。这也强调了一个重要特性就是–抽象。

4.依赖倒置原则

依赖倒置原则英文全称:Dependence Inversiion Principle 缩写:DIP

定义:指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。具体表现是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。其实就是面向接口编程的思想,可扩展性高,可以拥抱变化

5.接口隔离原则

接口隔离原则英文全称:InterfaceSegregation Principles 缩写:ISP

定义:客户端不应该依赖它不需要的接口。|| 类间的依赖关系应该建立在最小的接口上。就是一个接口别什么事都做,要不然实现它的子类将会有很多用不到的方法

6.迪米特原则

迪米特原则英文全称:Law of Demeter 缩写LOD

定义:一个对象应该对其他对象保持最少的了解。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。但是,过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值