遵循SOLID原则的写法

№1: 造一个人类

起初上帝造两个人:亚当和夏娃。如果上帝编码是为了创造人: 创造方法必须返回一个长度为2的数组,其中包含对象 代表亚当和夏娃。

数组中:

第一个对象应该是一个 "人 "类的实例。

第二个对象应该是女人类的一个实例。

这两个对象都必须是人类的子类。

你的工作是实现Human、Man和Woman类。

# 首先运行God函数返回的列表包含两个对象:
# 一个是男人,名字是亚当
# 一个是女人,名字是夏娃

def God():
    return [ Man("Adam"), Woman("Eva") ]
class Human:
   def __init__( self, name ):
      self.name = name

class Man( Human ):
   def __init__( self, name ):
      self.name = name

class Woman( Human ):
   def __init__( self, name ):
      self.name = name

第二种写法超简洁

def God():
    return [Man(), Woman()]

class Human(object):
    pass

class Man(Human):
    pass

class Woman(Human):
    pass

莫名感觉上帝是一名智慧的程序员!

№2:男人和女人

后来亚当和夏娃吃过苹果,意识到彼此身体的不同,急忙找树叶遮住身体。

Human并没有Gender属性,需要在 Man 和 Woman 类添加 gender属性:

class Human:
    def __init__(self, name):
       self.name = name

class Man(Human):
    def __int__(self,name,gender):
      self.name = name
      self.gender = 'Male'

class Woman(Human):
    def __int__(self,name,gender):
        self.name = name
        self.gender = 'Female'

Man 和 Woman 实例化

Adam = Man('Adam'#,'Male'
Eva = Woman('Eva')

print(Adam.name,Adam.gender)
print(Eva.gender)

#报错了!
AttributeError: 'Man' object has no attribute 'gender'

出错提示可见添加属性 gender 并没有成功!

修改为下面尝试:

class Human:
    def __init__(self, name):
        self.name = name

class Man(Human):
    def __init__(self, name):
        super().__init__(name)
        self.gender = 'Male'

class Woman(Human):
    def __init__(self, name):
        super().__init__(name) 
        self.gender = 'Female'

Man 和 Woman 实例化

Adam = Man('Adam'#,'Male'
Eva = Woman('Eva')
print(Adam.name,Adam.gender)
print(Eva.gender)

成功得到预期

Adam Male
Female

子类继承父类且扩展需要 super().__init__(name)

№3:灵活地设计优惠折扣方案

假设你有一家商店,你可以使用Discount类给顾客不同的优惠程度:

1、普通顾客 9 折:10% off

2、VIP打 8 折:20% off

3、Super VIP 是在 VIP 的折扣标准 * 2:40% off

注意到初学者自然地想到条件判断 if elif

但考虑到我们刚学过类的用法灵活扩展且稳定。决定改用类描述不同优惠力度的折扣:

class Discount {
    giveDiscount() {
        return this.price * 0.2
    }
}

当您决定向 VIP 客户提供双倍的 20% 折扣时。您可以这样修改该类:

class Discount {
    giveDiscount() {
        if(this.customer == 'fav') {
            return this.price * 0.2;
        }
        if(this.customer == 'vip') {
            return this.price * 0.4;
        }
    }
}

可惜这样写并没有遵循 OCP 原则。

OCP 禁止这样做。每当我们想给不同类型的客户提供增加新的折扣,你会发现需要添加一个新的逻辑。

为了遵循 OCP 原则,我们将添加一个新类来扩展折扣类。在这个新类中,我们将实现它的新行为:

class SuperVIPDiscount: VIPDiscount {
    getDiscount() {
        return super.getDiscount() * 2;
    }
}

以下是改用python实现:

同样也用条件判断实现时,缺乏灵活扩展的方便和稳定。

class Discount:
    def __init__(self, customer):
        self.customer = customer 

    def give_discount(self, price):
        if self.customer == 'fav':
            return price * 0.2
        if self.customer == 'vip':
            return price * 0.4
        return price * 0.1

discount = Discount('vip')
print(discount.give_discount(100))

改写为下面的:

class Discount:
    def get_discount(self):
        return 0.1  # 10% 折扣

class VIPDiscount(Discount):
    # VIP 顾客的折扣
    def get_discount(self):
        return super().get_discount() * 2  

class SuperVIPDiscount(VIPDiscount):
    # 超级 VIP 的折扣
    def get_discount(self):
        return super().get_discount() * 2

discount = SuperVIPDiscount()
print(discount.get_discount()) 

# 输出0.6,即40%折扣

如果VIP折扣变化,只修改Discount即可,无需修改VIPDiscountSuperVIPDiscount

№4: 雪地轮胎

假设有一个父类 Bicycle,Bicycle都有一组轮胎。所以,每次当一个新的自行车被初始化时,你会自动将轮胎分配给 GenericTires。看起来还不错吧?

但是当你想要一个不同类型的自行车和不同类型的轮胎时怎么办?

如果GenericTires接口发生了变化或者你想把它完全换掉怎么办?

你必须更新父Bicycle类。像这样梳理代码和更新硬编码元素是很乏味、不灵活和容易出错的。

使用工厂方法的目标是使对象更加可扩展和抽象,从而使它们更容易使用。

经验法则是:"在一个单独的操作中创建对象,这样子类就可以覆盖它们的创建方式" - 《设计模式》(1994年)

这正是我们要探讨如何在Python中做的事情。

初始设计

在第一个例子中,我们将介绍一段可以应用工厂方法的代码通常是什么样子的。我们将使用我们方便的Bicycleexample,类的模型是我们熟悉的物理事物时,更容易掌握这样的概念。

Sandi Metz也经常在示例代码中使用构建一辆自行车,我是这个类比的忠实粉丝。让我们潜心研究一下吧。

class Bicycle:
    def __init__(self):
        self.tires = GenericTires() 
        
    def get_tire_type(self):
        return self.tires. tire_type()

class GenericTires:
    def tire_type(self): 
        eturn 'generic'bike = Bicycle()
        
print(bike.get_tire_type())

父类Bicycle 初始化(initializes) 直接引用类的GenericTires集合。

这使得Bicycle和GenericTires之间的耦合更加紧密和严格。创建不同类型的自行车是可能的,但在本例中改变它们的轮胎并不直观。

假设我们想制造一种新的自行车。一辆山地车。它可能应该有一些特殊的轮胎来处理崎岖的地形,对吗?

我们如何调整基础Bicycle类,让它更灵活地适应不同种类的轮胎呢?

下面是一个稍微灵活的设计:

class Bicycle:
    def __init__(self):
        self.tires = self.add_tires()    
        
    def add_tires(self):
        return GenericTires()    

    def get_tire_type(self):
        return self.tires.tire_type()
        
class MountainBike(Bicycle):
    def add_tires(self):
        return MountainTires()
        
class GenericTires:
    def tire_type(self):
        return 'generic'
        
class MountainTires:
    def tire_type(self):
        return 'mountain'
        
mountain_bike = MountainBike()

print(mountain_bike.get_tire_type())
mountain 

在这个例子中,我们添加了一个新的add_tires()方法来返回合适的轮胎类。这让我们可以在Bicycle的任何一个子类中重写该方法,并提供一个不同的轮胎类。

为了添加一个新的自行车和轮胎,你只需要添加新的类并覆盖该方法。你不需要对基类进行调整。

接下来,让我们看看如何进一步扩展这个功能,以满足您拥有更复杂的自行车部件的需求。

更灵活的设计

在这里,我们展示了一种类似于抽象工厂模式的更加灵活和 "客户友好 "的设计。

这个例子为父类添加了一个工厂参数,它将接受一种 "零件工厂",用于指定要创建的轮胎或其他零件的类型。

class Bicycle:
    def __init__(self, factory):
        self.tires = factory().add_tires()
        self.frame = factory().add_frame()
        
class GenericFactory:
    def add_tires(self):
        return GenericTires()    
        
    def add_frame(self):
        return GenericFrame()
        
class MountainFactory:
    def add_tires(self):
        return RuggedTires()    
        
    def add_frame(self):
        return SturdyFrame()
        
class RoadFactory:
    def add_tires(self):
        return RoadTires()    
        
    def add_frame(self):
        return LightFrame()
        
class GenericTires:
    def part_type(self):
        return 'generic_tires'
        
class RuggedTires:
    def part_type(self):
        return 'rugged_tires'
        
class RoadTires:
    def part_type(self):
        return 'road_tires'
        
class GenericFrame:
    def part_type(self):
        return 'generic_frame'
        
class SturdyFrame:
    def part_type(self):
        return 'sturdy_frame'
        
class LightFrame:
    def part_type(self):
        return 'light_frame'

注释:

generic_tires - 普通轮胎
generic_frame - 普通车架 
rugged_tires - 坚固轮胎
sturdy_frame - 坚固车架
road_tires - 公路轮胎
light_frame - 轻量车架
这看起来像是自行车的一些部件名称。
generic和rugged后缀表示轮胎的普通和坚固版本。
generic和sturdy后缀表示车架的普通和坚固版本。
road表示公路轮胎,light表示轻量车架。

实例化并输出打印:

#Case_1st
bike = Bicycle(GenericFactory)
print(bike.tires.part_type())
generic_tires

print(bike.frame.part_type())
generic_frame

#Case_2nd
mountain_bike = Bicycle(MountainFactory)

print(mountain_bike.tires.part_type())
rugged_tires

print(mountain_bike.frame.part_type())
sturdy_frame

#Case_3rd 
road_bike = Bicycle(RoadFactory)
print(road_bike.tires.part_type())
road_tires

print(road_bike.frame.part_type())
light_frame

我们避免在父类中指定要安装的零件类型。

添加零件是由零件工厂里面各自的add_tires()和add_frame()方法处理的。

零件工厂只是一个单独的类,处理添加每个具体的零件。父类Bicycle调用零件工厂类内的方法来添加零件。

每一个实例仍然是一辆Bicycle,但用不同的零件构建。

你还可以采取一些额外的步骤来进一步抽象前一组类。

add_tires()add_frame()泛化为add_part('type')

父类 Bicycle 中创建一个方法,用于显示与自行车相关的所有部件。

您可以通过简单地获取每个part_type()或将self.tires和self.frame移动到一个零件列表或映射中来迭代来实现。

现在,Bicycle希望传递一个零件工厂。你可以在类里面包含GenericFactory作为默认的赋值,可以被新的工厂重写。

这些例子只是在Python中编写和扩展工厂模式的一些方法。

还有许多其他的变化。您选择哪一种完全取决于项目的大小和类的复杂程度。

在某些情况下,也许你只需要覆盖父类中的一个方法,在其他情况下,你需要一个专门的工厂类来实例化多个对象。

最终,无论你选择哪种方案,都应该感觉自然,并为客户提供一个简单的、可扩展的接口。

№5:几何形状的类

今天的任务是编程解一道简单的几何问题:

Equilateral等边形
Scalene斜边
Isosceles等腰三角形
Not a valid triangle不是一个有效的三角形

定义一个函数,输入是三角形的三条边的边长,输出为以上三种类型的字符串表达。注意输入有可能包含字符串等其他类型

type_of_triangle(1,1,1), "Equilateral")
type_of_triangle(3,2,4), "Scalene")
type_of_triangle(2,2,1), "Isosceles")
type_of_triangle('.',5,82), "Not a valid triangle")

算法梳理:

回忆三角形类型判断的几个规律:

任意两边之和要大于第三条边
任意两边只差要小于第三条边
三条边全部相同为等边三角形
只有两条边相同是等腰三角形
不满足3、4的即判为斜三角形

下面 3 种都是条件判断的娴熟运用,但不是我们推荐的写法:

def type_of_triangle(a, b, c):
    if not all([type(i)==int for i in (a,b,c)]):
        return "Not a valid triangle"

    if sum(sorted([a,b,c])[:2]) < max([a,b,c]):
        return "Not a valid triangle"
    else:
        if a == b == c:return "Equilateral"
        if a == b or a == c or b == c:return "Isosceles"
    return "Scalene"

#1st

def type_of_triangle(a, b, c):
    if any(not isinstance(x, int) for x in (a, b, c)):
        return "Not a valid triangle"
    a, b, c = sorted((a, b, c))
    if a + b <= c:
        return "Not a valid triangle"
    if a == b and b == c:
        return "Equilateral"
    if a == b or a == c or b == c:
        return "Isosceles"
    return "Scalene"

#2nd

def type_of_triangle(*S):
    try :
        S = sorted(S)
        if sum(S[:2])<=S[2]:                     return "Not a valid triangle"
        if all(e == S[0] for e in S[1:]) :       return "Equilateral"
        if any(a==b for a, b in zip(S, S[1:])) : return "Isosceles"
        else :                                   return "Scalene"
    except :                                     return "Not a valid triangle"

引入“类”的概念

对给定三边边长的三角形,可以方便的判断:是否既属于等腰三角形,又属于直角三角形。

这类多重属性的判断,将三角形的边长储存公共空间,只需要调用类中定义的函数,而不必象传统的函数调用,必须为函数传进参数:

# Type of Triangle
class Triangle:
    def __init__(self, a, b, c):
        self.a = a 
        self.b = b
        self.c = c

    def is_valid(self):
        return self.a + self.b > self.c and \
               self.a + self.c > self.b and \
               self.b + self.c > self.a

    def is_equilateral(self):
        return self.a == self.b == self.c
    
    def is_isosceles(self):
        return self.a == self.b or self.a == self.c or self.b == self.c 

    def type(self):
        if not self.is_valid():
            return "Not a valid triangle"
            
        if self.is_equilateral():
            return "Equilateral triangle"
        
        if self.is_isosceles(): 
            return "Isosceles triangle"
        
        return "Scalene triangle"
        
t = Triangle(3,4,5)
print(t.type())

扩展新的行为即:计算任意形状的三角形面积

只需添加area_of_triange函数到 Type of Triangle 代码段内:

class Triangle:
    ... ... 

    def area_of_triange(self):
        a,b,c = self.a,self.b,self.c
        return 0.25* math.sqrt((a + b + c)*(a + b - c)*(a + c - b)*(b + c - a))

Next Big thing-面向对象的编程!

链接内文末有Tim深受欢迎的 40min高清视频能够帮助每一位初学者体会到OOP的魅力:

了解class类的用法等。进入类、装饰器等较难掌握的技巧之前,请童鞋们仔细反复观看推荐给初学者的视频课程,类的用法超级灵活,真实场景中特别有用!

本文由 mdnice 多平台发布

你可能感兴趣的:(后端)