• 欢迎访问我的个人网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

编程设计相关(一)

编程开发 jason 714次浏览 0个评论

Why

Linus 说过,这世界程序员之所有高下之分,最大的区别就是程序员的“品味”不一样。有品位的程序员和没有品位的程序员所写出来的代码,所做出来的软件,差距非常大,而且价值也差别很大。

But

软件设计这个事,并不是一朝一夕就能学会的,也不是别人能把你教会的,很多东西需要你自己用实践、用时间、用错误、用教训、用痛苦才能真正体会其中的精髓的。所以,除了学习理论知识外,你还需要大量的工程实践,然后每过一段时间就把这些设计的东西重新回炉一下。你会发现这些软件设计的东西,就像饮茶一样,一开始是苦的,然后慢慢回甘,最终你会喝出真正的滋味。

六个编程范型将改变你对编程的看法

斯坦福大学公开课:编程范式 通过学习这门课程,你会对一些常用的编程范式有所了解。以 C 语言为例,它解释了 C 语言的基本要素,如指针、内存分配、堆、C 风格的字符串等,并解释了为什么 C 语言会在泛型编程、多态等方面有局限性。

软件设计的相关原则

Don’t Repeat Yourself (DRY)

DRY 是一个最简单的法则,也是最容易被理解的。但它也可能是最难被应用的(因为要做到这样,我们需要在泛型设计上做相当的努力,这并不是一件容易的事)。它意味着,当在两个或多个地方发现一些相似的代码的时候,我们需要把它们的共性抽象出来形成一个唯一的新方法,并且改变现有地方的代码让它们以一些合适的参数调用这个新的方法。

举一个DRY的典型例子,如果在一个类构造的时候,需要进行成员的初始化,在进行了某些操作以后,同样要进行初始化,那么就可以把“初始化”抽象出来,做成一个方法init(),在构造和需要用到的地方调用它。

class CsvValidation {
    public void validateProduct(List<String> product) {
        if (!isset(product['color'])) {
            throw new Exception('Import fail: the product attribute color is missing');
        }

        if (!isset(product['size'])) {
            throw new Exception('Import fail: the product attribute size is missing');
        }

        if (!isset(product['type'])) {
            throw new Exception('Import fail: the product attribute type is missing');
        }
    }
}

很多人有个误区, 认为 DRY 仅仅是指代码是的重复, 其实不是, 还有一方面是认知上的重复。

/** Shipment from the warehouse to the customer */
class Shipment {
 public int deliveryTime = 4; //in days
 public DateTime calculateDeliveryDay() {
 return new DateTime("now day" + this->deliveryTime);
 }
}
/** Order return of a customer */
class OrderReturn {
 public int returnLimit = 4; //in days
 public DateTime calculateLastReturnDay() {
 return new DateTime("now day" + this->returnLimit);
 }
}

Program to an interface, not an implementation

这是设计模式中最根本的哲学,注重接口,而不是实现,依赖接口,而不是实现。接口是抽象是稳定的,实现则是多种多样的。在面向对象的 S.O.L.I.D 原则中会提到我们的依赖倒置原则,就是这个原则的另一种样子。还有一条原则叫 组合而不是继承,这两条是那 23 个经典设计模式中的设计原则。

public ProcessedTea processTea(Tea tea){
 ProcessedTea processedTea = tea.getProcessedTea();
 return processedTea;
}
public ProcessedFood processFood(FoodItem foodItem){
 ProcessedFood processedFood = foodItem.getProcessedFood();
 return processedFood;
}

这样是不是就只需要Tea 继承 FoodItem, ProcessedTea 继承 ProcessedFood 呢, 如上面生成其他类型事物,就要提交一种方法,破坏封装性

迪米特法则 (Law of Demeter)

迪米特法则 (Law of Demeter),又称“最少知识原则”(Principle of Least Knowledge)

关于迪米特法则有一些很形象的比喻:1) 如果你想让你的狗跑的话,你会对狗狗说还是对四条狗腿说? 如果你去店里买东西,你会把钱交给店员,还是会把钱包交给店员让他自己拿?和狗的四肢说话?让店员自己从钱包里拿钱?这听起来有点儿荒唐,不过在我们的代码里这几乎是见怪不怪的事情了。对于 LoD,正式的表述如下:

对于对象 ‘O’ 中一个方法’M’,M 应该只能够访问以下对象中的方法:

  • 对象 O;

  • 与 O 直接相关的 Component Object;

  • 由方法 M 创建或者实例化的对象; 作为方法 M 的参数的对象
/**
 * A Law of Demeter example in Java.
 * Created by Alvin Alexander, http://alvinalexander.com.
 * This is an adaptation of the source code example from the book
 * The Pragmatic Programmer.
 */
public class LawOfDemeterInJava {
 private Topping cheeseTopping;
 /**
 * Good examples of following the Law of Demeter.
 */
 public void goodExamples(Pizza pizza) {
 Foo foo = new Foo();
 // (1) it's okay to call our own methods
 doSomething();
 // (2) it's okay to call methods on objects passed in to our method
 int price = pizza.getPrice();
 // (3) it's okay to call methods on any objects we create
 cheeseTopping = new CheeseTopping();
 float weight = cheeseTopping.getWeightUsed();
 // (4) any directly held component objects
 foo.doBar();
 }
 private void doSomething() {
 // do something here ...
 }
}

错误例子


<pre class="prettyprint lang-js">objectA.getObjectB().doSomething();
objectA.getObjectB().getObjectC().doSomething();
public class Band {
 private Singer singer;
 private Drummer drummer;
 private Guitarist guitarist;
}
guitaristName = band.getGuitarist().getName();

<p style="font-size:14px;color:#384452;font-family:-apple-system, system-ui, "">
	好的做法应该是在 Bank 单独定义一个方法<span style="font-family:Consolas, Monaco, "font-size:14.52px;"> </span> 
</p>

<pre class="prettyprint linenums prettyprinted">
<ol class="linenums" style="margin-left:0px;color:#8E908C;">
	
</ol>

public String getGuitaristName() {
 return guitarist.getName();
}

面向对象的 S.O.L.I.D 原则

维基百科

在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则。

SRP(Single Responsibility Principle)- 职责单一原则

关于单一职责原则,其核心的思想是:一个类,只做一件事,并把这件事做好,其只有一个引起它变化的原因。单一职责原则可以看作是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。 职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而极大地损伤其内聚性和耦合度。单一职责,通常意味着单一的功能,因此不要为一个模块实现过多的功能点,以保证实体只有一个引起它变化的原因。

public class Employee {
 private String employeeId;
 private String name;
 private string address;
 private Date dateOfJoining;
 public boolean isPromotionDueThisYear(){
 //promotion logic implementation
 }
 public Double calcIncomeTaxForCurrentYear(){
 //income tax logic implementation
 }
 //Getters & Setters for all the private attributes
}

我们来看一下上面问题:

  • isPromotionDueThisYear 员工加薪是员工自己的功能吗? 如果有, 那这个人肯定不是员工, 那是老板,当然这是是的这个 HR 部门的事情

  • calcIncomeTaxForCurrentYear 员工的计算工资是员工的功能吗? 那是财务部门的功能

		
<ol class="linenums" style="margin-left:0px;color:#8E908C;">
	
</ol>
public class HRPromotions{
 public boolean isPromotionDueThisYear(Employee emp){
 //promotion logic implementation using the employee information passed
 }
}
public class FinITCalculations{
 public Double calcIncomeTaxForCurrentYear(Employee emp){
 //income tax logic implementation using the employee information passed
 }
}

OCP(Open/Closed Principle)- 开闭原则

关于开发封闭原则,其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

public class Rectangle {
 public double Width { get; set; }
 public double Height { get; set; }
}
public class AreaCalculator {
 public double area(Rectangle[] shapes)
 {
 double area = 0;
 foreach (var shape in shapes)
 {
 area += shape.Width*shape.Height;
 }
 return area;
 }
}

上面只能技术长方形的面积, 现在让你计算一个园的面积, 你说没事, 我会这样写:

<ol class="linenums" style="margin-left:0px;color:#8E908C;font-family:Consolas, Menlo, Courier, monospace;font-size:10px;">
	
	<li class="L0" style="color:#384452;background-color:#EEF1F5;">
		public double area(object[] shapes){
	</li>

	<li class="L1" style="color:#384452;background-color:#EEF1F5;">
		 double area = 0;
	</li>

	<li class="L2" style="color:#384452;background-color:#EEF1F5;">
		 foreach (var shape in shapes) {
	</li>

	<li class="L3" style="color:#384452;background-color:#EEF1F5;">
		 if (shape is Rectangle) {
	</li>

	<li class="L4" style="color:#384452;background-color:#EEF1F5;">
		 Rectangle rectangle = (Rectangle) shape;
	</li>

	<li class="L5" style="color:#384452;background-color:#EEF1F5;">
		 area += rectangle.Width*rectangle.Height;
	</li>

	<li class="L6" style="color:#384452;background-color:#EEF1F5;">
		 } else {
	</li>

	<li class="L7" style="color:#384452;background-color:#EEF1F5;">
		 Circle circle = (Circle)shape;
	</li>

	<li class="L8" style="color:#384452;background-color:#EEF1F5;">
		 area += circle.Radius * circle.Radius * Math.PI;
	</li>

	<li class="L9" style="color:#384452;background-color:#EEF1F5;">
		 }
	</li>

	<li class="L0" style="color:#384452;background-color:#EEF1F5;">
		 }
	</li>

	<li class="L1" style="color:#384452;background-color:#EEF1F5;">
		
	</li>

	<li class="L2" style="color:#384452;background-color:#EEF1F5;">
		 return area;
	</li>

	<li class="L3" style="color:#384452;background-color:#EEF1F5;">
		}
	</li>

</ol>

<ol class="linenums" style="margin-left:0px;color:#8E908C;">
	
</ol>

那如果在记忆中椭圆的面积计算呢? 其实这里也违背了下面我要讲的 LSP(里氏代换原则)

<ol class="linenums" style="margin-left:0px;color:#8E908C;">
	
</ol>
public interface Shape {
 double area();
}
public class Rectangle implement Shape {
 public double Width { get; set; }
 public double Height { get; set; }
 @override
 public double area() {
 return Width * Height;
 }
}
public class Circle implement Shape {
 public double Radius { get; set; }
 @override
 public double area() {
 return Radius * Radius * Math.PI;
 }
}
public double area(Shape[] shapes) {
 double area = 0;
 foreach (var shape in shapes) {
 area += shape.Area();
 }
 return area;
}

LSP(Liskov substitution principle)- 里氏代换原则

软件工程大师罗伯特·马丁(Robert C. Martin)把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for their base types”。也就是,子类必须能够替换成它们的基类。即子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。另外,不应该在代码中出现 if/else 之类对子类类型进行判断的条件。里氏替换原则 LSP 是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。

ISP(Interface Segregation Principle )- 接口隔离原则

接口隔离原则的意思是把功能实现在接口中,而不是类中,使用多个专门的接口比使用单一的总接口要好。举个例子,我们对电脑有不同的使用方式,比如:写作、通讯、看电影、打游戏、上网、编程、计算和数据存储等。

<ol class="linenums" style="margin-left:0px;color:#8E908C;">
	
</ol>
public interface RestaurantInterface {
 public void acceptOnlineOrder();
 public void takeTelephoneOrder();
 public void payOnline();
 public void walkInCustomerOrder();
 public void payInPerson();
}
public class OnlineClientImpl implements RestaurantInterface{
 @Override
 public void acceptOnlineOrder() {
 //logic for placing online order
 }
 @Override
 public void takeTelephoneOrder() {
 //Not Applicable for Online Order
 throw new UnsupportedOperationException();
 }
 @Override
 public void payOnline() {
 //logic for paying online
 }
 @Override
 public void walkInCustomerOrder() {
 //Not Applicable for Online Order
 throw new UnsupportedOperationException();
 }
 @Override
 public void payInPerson() {
 //Not Applicable for Online Order
 throw new UnsupportedOperationException();
 }
}

20180928134308_23046 编程设计相关(一)


喜欢 (2)