Skip to content

面相对象与面相过程

OOPvsPOP

前言

相信大家在工作学习中应该经常听到身边的同事说一些关于面向对象的事,其中应该大部分都是好处。

这点我是感觉尤为深刻,因为我代码其实写的比较一般,虽说在大学时也学习过面向对象思想,无非是”封装、集成、多态“嘛,概念是理解的,但是在平时开发中还是一样会写出一手习惯性的”面向过程“代码,缺少的就是很关键的抽象思维能力。

导致我在遇到难以解决的代码问题时,请教身边的同事,同事比较经常的回答是:”你这段代码已经不好维护了,是面向过程的,你应该把 xxx 做个抽象,你现在维护要花费很长的时间,建议你直接根据面相对象的方式进行重构“。在重构之后果然之前的一些不好扩展的代码就很好解决了。

所以我对于面向对象的理解最直观的一点就是:这样写的优势在于后续迭代和维护的成本大大降低了。

在这里告诫兄弟们一下,写需求时一定不能是 ”能跑就行“ 了,需求是多变的,当下虽然能跑了,后续迭代说不定就不能跑了,这时候再去重构真的是个非常痛苦的事情。

正文

最近看《代码简洁之道》这本书,其中有一个非常不错的例子,就是面向对象和面向过程代码。跟大家简单的分享一下,代码的目的是计算:正方形、长方形、三角形这三个几何图形的面积:

先看实现 1:

ts
interface Point {
	x: number
	y: number
}

class Square {
	constructor(public point: Point, public side: number) {}
}

class Rectangle {
	constructor(
		public point: Point,
		public width: number,
		public height: number
	) {}
}

class Circle {
	constructor(public center: Point, public radius: number) {}
}

class Geometry {
	private PI: number = 3.14

	public area(obj: any): number | undefined {
		if (obj instanceof Square) {
			return obj.side * obj.side
		} else if (obj instanceof Rectangle) {
			return obj.width * obj.height
		} else if (obj instanceof Circle) {
			return obj.radius * obj.radius * this.PI
		}
	}
}

export const geometry = new Geometry()

export const square = new Square({ x: 0, y: 0 }, 10)

export const rectangle = new Rectangle({ x: 0, y: 0 }, 10, 8)

export const circle = new Circle({ x: 0, y: 0 }, 3)
interface Point {
	x: number
	y: number
}

class Square {
	constructor(public point: Point, public side: number) {}
}

class Rectangle {
	constructor(
		public point: Point,
		public width: number,
		public height: number
	) {}
}

class Circle {
	constructor(public center: Point, public radius: number) {}
}

class Geometry {
	private PI: number = 3.14

	public area(obj: any): number | undefined {
		if (obj instanceof Square) {
			return obj.side * obj.side
		} else if (obj instanceof Rectangle) {
			return obj.width * obj.height
		} else if (obj instanceof Circle) {
			return obj.radius * obj.radius * this.PI
		}
	}
}

export const geometry = new Geometry()

export const square = new Square({ x: 0, y: 0 }, 10)

export const rectangle = new Rectangle({ x: 0, y: 0 }, 10, 8)

export const circle = new Circle({ x: 0, y: 0 }, 3)

从以上的代码可以看出,关键的计算逻辑在于Geometry类中的area方法中。通过 new 一个 Geometry 相当于得到一个计算器,之后传不同的几何对象,可以算出对象图形的面积。

面相对象程序员可能一眼就会看出代码的问题,原因就是这段代码非常的难以扩展,虽然说现在是”能跑“,但是这里发出两个灵魂拷问:

  • 如果后续要计算周长呢?
  • 如果后续添加其他的几个图形呢?

后续如果需要迭代这两个功能,甚至更多的需求,这代码就相对的难以维护了,会让这个类变得又大又长!应该使用面对对象的思路进行抽象。再看实现 2:

实现 2:

ts
interface Point {
	x: number
	y: number
}

interface Shape {
	area: () => number
}

class Square implements Shape {
	constructor(public point: Point, public side: number) {}
	area() {
		return this.side * this.side
	}
}

class Rectangle implements Shape {
	constructor(
		public point: Point,
		public width: number,
		public height: number
	) {}
	area() {
		return this.width * this.height
	}
}

class Circle implements Shape {
	private PI: number = 3.14
	constructor(public center: Point, public radius: number) {}
	area() {
		return this.radius * this.radius * this.PI
	}
}

export const square = new Square({ x: 0, y: 0 }, 10)

export const rectangle = new Rectangle({ x: 0, y: 0 }, 10, 8)

export const circle = new Circle({ x: 0, y: 0 }, 3)
interface Point {
	x: number
	y: number
}

interface Shape {
	area: () => number
}

class Square implements Shape {
	constructor(public point: Point, public side: number) {}
	area() {
		return this.side * this.side
	}
}

class Rectangle implements Shape {
	constructor(
		public point: Point,
		public width: number,
		public height: number
	) {}
	area() {
		return this.width * this.height
	}
}

class Circle implements Shape {
	private PI: number = 3.14
	constructor(public center: Point, public radius: number) {}
	area() {
		return this.radius * this.radius * this.PI
	}
}

export const square = new Square({ x: 0, y: 0 }, 10)

export const rectangle = new Rectangle({ x: 0, y: 0 }, 10, 8)

export const circle = new Circle({ x: 0, y: 0 }, 3)

这里将内容将所有图形进行了抽象成一个 Shape 后续的图形只要只需要都实现这个接口即可,再在各自的类中实现对应的面积方法,后面如需迭代 周长、等其他类型只需要在 Shape 上新增对应的周长方法定义,再在各个类上实现即可。避免了又大又长的代码。利于维护。

总结

采用面向对象的方式实现一些功能或需求对于传统面向过程的实现方式来说本身就是一种对代码的优化。但是需要一定的抽象能力,这个抽象能力就是我们作为程序员需要培养的。