面向对象编程(OOP)是C ++,Java,C#,Visual Basic .NET,Ruby,Objective-C甚至Swift语言的核心。他们不能没有网络技术对象,因为它们使用JavaScript,Python和PHP。这就是为什么Matt Weissfeld建议开发一种面向对象的思维方式,然后才使用特定的编程语言进行面向对象的开发的原因。本书是由开发人员为开发人员编写的,可让您选择解决特定问题的最佳方法。您将学习如何正确应用继承和组合,了解聚合和关联之间的区别,以及避免混淆接口和实现。编程技术在不断变化和发展,但是面向对象的概念与平台无关,并且始终有效。本出版物重点介绍OOP的基本原理:设计模式,依赖关系和SOLID原理,这些原理将使您的代码易于理解,灵活且易于维护。SOLID面向对象的设计原则
1. SRP:唯一责任原则
唯一责任原则指出,仅需一个理由即可更改班级。每个类和程序模块必须具有一个优先级的任务。因此,不应出于多种原因引入可导致类更改的方法。如果类描述包含单词“ and”,则可能违反SRP原则。换句话说,每个模块或类都必须负责软件功能的一部分,并且此类责任必须完全封装在类中。创建图的层次结构是说明继承的经典示例之一,该示例经常在教学中找到,我在本章(以及整本书中)都使用它。在此示例中,Circle类从Shape类继承属性。Shape类提供抽象的calcArea()方法作为子类的协定。从Shape继承的每个类都必须具有自己的calcArea()方法的实现:abstract class Shape{
protected String name;
protected double area;
public abstract double calcArea();
}
在此示例中,从Shape类继承的Circle类在必要时提供其对calcArea()方法的实现:class Circle extends Shape{
private double radius;
public Circle(double r) {
radius = r;
}
public double calcArea() {
area = 3.14*(radius*radius) ;
return (area);
};
}
第三类CalculateAreas计算Shape数组中包含的各种形状的面积。Shape数组的大小不受限制,可以包含各种形状,例如正方形和三角形。class CalculateAreas {
Shape[] shapes;
double sumTotal=0;
public CalculateAreas(Shape[] sh) {
this.shapes = sh;
}
public double sumAreas() {
sumTotal=0;
for (inti=0; i<shapes.length; i++) {
sumTotal = sumTotal + shapes[i].calcArea() ;
}
return sumTotal ;
}
public void output() {
System.out.printIn("Total of all areas = " + sumTotal);
}
}
请注意,CalculateAreas类还处理应用程序输出,这可能会导致问题。面积计数行为和输出行为是相关的,因为它们包含在同一类中。我们可以使用适当的测试应用程序TestShape验证此代码的功能:public class TestShape {
public static void main(String args[]) {
System.out.printin("Hello World!");
Circle circle = new Circle(1);
Shape[] shapeArray = new Shape[1];
shapeArray[0] = circle;
CalculateAreas ca = new CalculateAreas(shapeArray) ;
ca.sumAreas() ;
ca.output();
}
}
现在我们有一个测试应用程序可供使用,我们可以专注于唯一责任原则的问题。同样,问题出在CalculateAreas类上,该类包含用于将各种形状的区域相加以及输出数据的行为。基本的问题(实际上是问题)是,如果您需要更改output()方法的功能,则无论计算形状面积的方法是否发生变化,都需要对CalculateAreas类进行更改。例如,如果我们突然想要将数据输出到HTML控制台而不是纯文本,则需要重新编译并重新实现代码,这会增加图形的面积。都是因为责任是相关的。按照唯一责任原则,任务是更改一种方法不会影响另一种方法,并且不必重新编译。 “班级应该只有一个改变的理由,这是唯一需要改变的责任。”要解决此问题,可以将两个方法放在单独的类中,一个用于原始控制台输出,另一个用于HTML输出:class CaiculateAreas {;
Shape[] shapes;
double sumTotal=0;
public CalculateAreas(Shape[] sh) {
this.shapes = sh;
}
public double sumAreas() {
sumTotal=0;
for (inti=0; i<shapes.length; i++) {
sumTotal = sumTotal + shapes[i].calcArea();
}
return sumTotal;
}
}
class OutputAreas {
double areas=0;
public OutputAreas (double a) {
this.areas = a;
}
public void console() {
System.out.printin("Total of all areas = " + areas);
}
public void HTML() {
System.out.printIn("<HTML>") ;
System.out.printin("Total of all areas = " + areas);
System.out.printin("</HTML>") ;
}
}
最重要的是,现在您可以根据需要向不同的方向发送结论。如果要添加其他输出方法(例如JSON)的可能性,则可以将其添加到OutputAreas类,而无需对CalculateAreas类进行更改。因此,您可以重新分配CalculateAreas类,而不会以任何方式影响其他类。2. OCP:开放/封闭原则
开放性/紧密性的原则指出,您可以扩展类的行为而无需进行更改。让我们再次关注带有数字的示例。在下面的代码中,有一个ShapeCalculator类,它接收一个Rectangle对象,计算该对象的面积并返回值。这是一个简单的应用程序,但仅适用于矩形。class Rectangle{
protected double length;
protected double width;
public Rectangle(double 1, double w) {
length = 1;
width = w;
};
}
class CalculateAreas {
private double area;
public double calcArea(Rectangle r) {
area = r.length * r.width;
return area;
}
}
public class OpenClosed {
public static void main(String args[]) {
System.out.printin("Hello World");
Rectangle r = new Rectangle(1,2);
CalculateAreas ca = new CalculateAreas ();
System.out.printin("Area = "+ ca.calcArea(r));
}
}
该应用程序仅在矩形的情况下起作用的事实导致了一个局限性,该局限性清楚地说明了开放性/闭合性的原理:如果我们想将Circle类添加到CalculateArea类(更改其功能),则需要对模块本身进行更改。显然,这与开放性/封闭性原则相冲突,开放性/封闭性原则指出我们不应更改模块以更改其功能。为了遵守开放性/封闭性原则,我们可以返回已经测试过的图形实例,其中创建了一个抽象Shape类,并且直接从Shape类继承了图形,该Shape类具有一个抽象getArea()方法。目前,您可以根据需要添加任意多个不同的类,而无需直接对Shape类(例如Circle类)进行更改。现在我们可以说Shape类已关闭。以下代码为矩形和圆形提供了解决方案的实现,并允许您创建无限数量的形状:abstract class Shape {
public abstract double getArea() ;
}
class Rectangle extends Shape
{
protected double length;
protected double width;
public Rectangle(double 1, double w) {
length = 1;
width = w;
};
public double getArea() {
return length*width;
}
}
class Circle extends Shape
{
protected double radius;
public Circle(double r) {
radius = r;
};
public double getArea() {
return radius*radius*3.14;
}
}
class CalculateAreas {
private double area;
public double calcArea(Shape s) {
area = s.getArea();
return area;
}
}
public class OpenClosed {
public static void main(String args[]) {
System.out.printiIn("Hello World") ;
CalculateAreas ca = new CalculateAreas() ;
Rectangle r = new Rectangle(1,2);
System.out.printIn("Area = " + ca.calcArea(r));
Circle c = new Circle(3);
System.out.printIn("Area = " + ca.calcArea(c));
}
}
值得注意的是,使用此实现,在创建Shape类的新实例时,不应修改CalculateAreas()方法。您可以扩展代码而不必担心先前代码的存在。开放性/封闭性的原则是您应该使用子类扩展代码,以使原始类不需要进行编辑。但是,在有关SOLID原理的一些讨论中,“扩展”概念本身存在争议。坦率地说,如果我们更喜欢组合而不是继承,这如何影响开放性/封闭性原则?遵循SOLID原则之一时,代码可能满足其他SOLID原则的条件。例如,在按照开放性/封闭性原则进行设计时,代码可能符合唯一责任原则的要求。3. LSP:Lisk替代原则
根据Liskov替换原则,设计应提供使用父类之一的实例替换父类的任何实例的可能性。如果父类可以执行任何任务,则子类也必须能够执行。乍一看一些正确的代码,但违反了Lisk替换原则。下面的代码包含通用的Shape抽象类。反过来,Rectangle类从Shape类继承属性,并覆盖其抽象的calcArea()方法。而Square类则继承自Rectangle。abstract class Shape{
protected double area;
public abstract double calcArea();
}
class Rectangle extends Shape{
private double length;
private double width;
public Rectangle(double 1, double w) {
length = 1;
width = w;
}
public double calcArea() {
area = length*width;
return (area) ;
};
}
class Square extends Rectangle{
public Square(double s) {
super(s, Ss);
}
}
public class LiskovSubstitution {
public static void main(String args[]) {
System.out.printIn("Hello World") ;
Rectangle r = new Rectangle(1,2);
System.out.printin("Area = " + r.calcArea());
Square s = new Square(2) ;
System.out.printin("Area = " + s.calcArea());
}
}
到目前为止,一切都很好:矩形是图形的一个实例,因此不必担心,因为正方形是矩形的一个实例-一切都正确,对吗?现在让我们问一个哲学问题:正方形还是矩形吗?许多人会回答是肯定的。尽管可以假定正方形是矩形的特例,但其属性会有所不同。矩形是一个平行四边形(相对的边是相同的),就像一个正方形。同时,正方形也是菱形(所有边都相同),而矩形不是。因此,存在差异。对于面向对象的设计,问题不在于几何。问题是我们如何精确地创建矩形和正方形。这是Rectangle类的构造函数:public Rectangle(double 1, double w) {
length = 1;
width = w;
}
显然,构造函数需要两个参数。但是,即使父类Rectangle需要两个,Square类的构造函数也只需要一个。class Square extends Rectangle{
public Square(double s) {
super(s, Ss);
}
实际上,在这两类中的每一种的情况下,用于计算面积的功能都略有不同。也就是说,Square类确实模仿了Rectangle,将相同的参数两次传递给构造函数。这样的解决方法似乎很合适,但是实际上,它可能会误导附带代码的开发人员,而将来在附带代码时,会充满陷阱。至少这是一个问题,并且可能是一个可疑的设计决策。当一个构造函数调用另一个构造函数时,最好休息一下并重新考虑该构造-子类可能未正确构建。如何找到摆脱这种情况的出路?简而言之,您不能用Square类代替Rectangle。因此,Square不应是Rectangle类的子级。它们必须是单独的类。abstract class Shape {
protected double area;
public abstract double calcArea();
}
class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(double 1, double w) {
length = 1;
width = w;
}
public double calcArea() {
area = length*width;
return (area);
};
}
class Square extends Shape {
private double side;
public Square(double s) {
side = s;
}
public double calcArea() {
area = side*side;
return (area);
};
}
public class LiskovSubstitution {
public static void main(String args[]) {
System.out.printIn("Hello World") ;
Rectangle r = new Rectangle(1,2);
System.out.printIn("Area = " + r.calcArea());
Square s = new Square(2) ;
System.out.printIn("Area = " + s.calcArea());
}
}
4. ISP:接口共享原则
接口分离的原理指出,创建多个小接口比创建多个大接口更好。在此示例中,我们创建一个接口,其中包含Mammal类的几种行为,即eat()和makeNoise():interface IMammal {
public void eat();
public void makeNoise() ;
}
class Dog implements IMammal {
public void eat() {
System.out.printIn("Dog is eating");
}
public void makeNoise() {
System.out.printIn("Dog is making noise");
}
}
public class MyClass {
public static void main(String args[]) {
System.out.printIn("Hello World");
Dog fido = new Dog();
fido.eat();
fido.makeNoise()
}
}
您需要为所有行为创建单独的接口,而不是为Mammal类创建单个接口:interface IEat {
public void eat();
}
interface IMakeNoise {
public void makeNoise() ;
}
class Dog implements IEat, IMakeNoise {
public void eat() {
System.out.printIn("Dog is eating");
}
public void makeNoise() {
System.out.printIn("Dog is making noise");
}
}
public class MyClass {
public static void main(String args[]) {
System.out.printIn("Hello World") ;
Dog fido = new Dog();
fido.eat();
fido.makeNoise();
}
}
我们将行为与哺乳动物班分开。事实证明,与其通过继承(更确切地说,是接口)创建单个Mammal类,我们不如上一章中所使用的策略,而是基于组合进行设计。简而言之,通过这种方法,我们可以使用合成来创建哺乳动物类的实例,而不是被迫使用嵌入在单个哺乳动物类中的行为。例如,假设发现哺乳动物不吃东西,而是通过皮肤吸收营养。如果我们从包含eat()行为的Mammal类继承,则此行为对于新哺乳动物将是多余的。而且,如果将所有行为都布置在单独的单个界面中,那么结果将完全按照预期来构建每种哺乳动物的类别。»这本书的更多信息可以在出版商的网站上找到» 目录» Habrozhitelami的节录可在优惠券上享受25%的折扣-OOP支付了纸质版本的书后,就会通过电子邮件发送电子书。