面向对象与网络编程
多态
多态指一类事物有多种形态
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
多态性
什么是多态性(在继承的北京下使用时,有时候也称为多态)
多态性是指在不考虑实例的情况下使用实例
在面向对象方法中一般时这样表述多态:
向不同的对象发送同意消息(obj.func():是调用了obj的方法func,又称为想obj发送了一条消息func),不同的对象在接受时叶猴产生不同的行为(即方法)。
例如:老师,下课铃声响了(),学生,下课铃声响了,老师执行的是小班操作,学生执行的是放学操作,虽然两者消息一样,但是执行的效果不同。
多态性分为静态多态性和动态多态
静态多态性:如任何类型都可以运用算符+进行计算
动态多态性:
例如:
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
为什么要用多态性(多态性的好处)
从这里可以看出,python本身就是支持多态性的。
优点:
- 1增加了程序的灵活性
以不变应万变,不论对象前边万化,使用着都是同一种形式去调用。- 增加了程序的可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,用同一种形式去掉就可以。
>>> class Cat(Animal): #属于动物的另外一种形态:猫
... def talk(self):
... print('say miao')
...
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
... animal.talk()
...
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao
'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
'''
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
例2:血泪类型有多种形态:字符串,列表,元组,但他们没有直接继承关系
#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
封装
封装对象的属性和实现细节,对外提供公共访问方式。
优点:
- 1.将变化隔离;
- 2.便于使用;
- 3.提高服用性;
- 4.提高安全性;
封装原则- 1.将不需要对外提供的内容都隐藏起来;
- 2.把属性都隐藏,提供公共方法对其访问;
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
私有变量
#所谓的私有变量看起来很高大上,其实就是在定义变量的时候加上双下滑线。
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
- 1.类中定义的__x只能在内部使用,例如:self.__x,引用的就是变形的结果。
- 2.这种变形其实是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 3.在子类定义的__x不会覆盖在父类定义的__x,应为子类中变形成了:_子类名__x,而父类中变形成了:__父类名__x,即双下滑线开头的属性在继承给子类时,子类时无法覆盖的。
需要注意的是问题:
- 1.这种机制也没有真正意义上限制,我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:__类名__属性,然后就可以访问了,如,a._A__N
- 2.变形的过程只在类的定义时发生一次,在定义后的复制操作,不会变形。
- 3 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定义成私有的,即__fa
>>> class A:
... def __fa(self): #在定义时就变形为_A__fa
... print('from A')
... def test(self):
... self.__fa() #只会与自己所在的类为准,即调用_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
封装不是单纯意义的隐藏
1.封装数据:
将数据隐藏起来这不是目的,隐藏起来然后对外提供操作数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
t=Teacher('egon',18)
t.tell_info()
t.set_info('egon',19)
t.tell_info()
封装方法:目的时隔离复杂度
在编程语言里,对外提供的接口(接口可以理解为一个入口),可以时函数,称之为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
隔离复杂度的例子
pyton并不会真的阻止你访问私有的属性,模块也遵顼这种约定,如果模块名称以下划线开头,那么from module import * 时不能被导入,但是你from module import _private__module依然可以导入。
其实很多时候你去调用一个模块的功能时候,都会遇到下划线开头的。
(socket._socket,sys.home,sys.clear_type_cache),这都是私有的,原则上是提供内部调用的,作为外部的你,一意孤行也是可以的,只不过看起来像个傻逼。
python要想与其他编程语言一样,严格控制属性的访问权限,也只能借助内置方法如__getattr。
特性(property)
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例如:
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
@property
def bmi(self):
return self.weight / (self.height**2)
p1=People('egon',75,1.85)
print(p1.bmi)
例如:圆的周长和面积
import math
class Circle:
def __init__(self,radius): #圆的半径radius
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
抛出异常:
AttributeError: can't set attribute
为什要用property
讲一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一的访问的原则
除此之外,你看:
#面向对象的封装有三种方式:
【public】
这种其实就是不封装,对外公开的
【protected】
这种封装方式对外不公开,但对朋友或者子类(形象的说法是“儿子”,但是我不知道为什么大家不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是“父类”)公开。
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
了解以下property
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
def getname(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
def setname(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
def delname(self):
raise TypeError('Can not delete')
name=property(getname,setname,delname) #不如装饰器的方式清晰
了解:一种property的古老用法
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()
在类中定义的函数分成两大类
绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
- 1.绑定到类方法:用classmetod装饰其来修饰的方法为类量身定制
类,boud_method(),自动将类当作第一个参数传入(其实对象也可调用,但将类作为第一个参数传入)- 2.绑定到对象的方法:没有任何装饰器装饰的方法。为对象量身定制。对像.boud_method(),自动将对象当作第一个参数传入(属于类的对象,类可以调用,但是必须按照函数的规则来,没有自动处值那么一说)
非绑定方法:用staticmethod装饰器装饰的方法
- 1.不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
绑定方法
绑定给类的方法(classmethod)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
#settings
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
例如:
#classmethod
class Classmethod_Demo():
role = 'dog'
@classmethod
def func(cls):
print(cls.role)
Classmethod_Demo.func()
非绑定方法
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一个普通工具
m=hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
例如:
#staticmeethod
class Staticmethod_Demo():
role = 'dog'
@staticmethod
def func():
print("当普通方法用")
Staticmethod_Demo.func()
classmethod与staticmethod的区别
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@staticmethod
def from_conf():
return MySQL(settings.HOST,settings.PORT)
# @classmethod #哪个类来调用,就将哪个类当做第一个参数传入
# def from_conf(cls):
# return cls(settings.HOST,settings.PORT)
def __str__(self):
return '就不告诉你'
class Mariadb(MySQL):
def __str__(self):
return '<%s:%s>' %(self.host,self.port)
m=Mariadb.from_conf()
print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你:
mariadb是mysql
练习
定义MySQL类
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象
原文链接:http://www.cnblogs.com/dkblog/archive/2011/10/10/2205200.html
Python官方Doc:《20.15. uuid — UUID objects according to RFC 4122》
UUID的算法介绍:《A Universally Unique IDentifier (UUID) URN Namespace》
概述:
UUID是128位的全局唯一标识符,通常由32字节的字符串表示。
它可以保证时间和空间的唯一性,也称为GUID,全称为:
UUID —— Universally Unique IDentifier Python 中叫 UUID
GUID —— Globally Unique IDentifier C# 中叫 GUID
它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。
UUID主要有五个算法,也就是五种方法来实现:
1、uuid1()——基于时间戳
由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性,
但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。
2、uuid2()——基于分布式计算环境DCE(Python中没有这个函数)
算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。
实际中很少用到该方法。
3、uuid3()——基于名字的MD5散列值
通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,
和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。
4、uuid4()——基于随机数
由伪随机数得到,有一定的重复概率,该概率可以计算出来。
5、uuid5()——基于名字的SHA-1散列值
算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法
使用方面:
首先,Python中没有基于DCE的,所以uuid2可以忽略;
其次,uuid4存在概率性重复,由无映射性,最好不用;
再次,若在Global的分布式计算环境下,最好用uuid1;
最后,若有名字的唯一性要求,最好用uuid3或uuid5。
编码方法:
# -*- coding: utf-8 -*-
import uuid
name = "test_name"
namespace = "test_namespace"
print uuid.uuid1() # 带参的方法参见Python Doc
print uuid.uuid3(namespace, name)
print uuid.uuid4()
print uuid.uuid5(namespace, name)
创建唯一id之UUID
#settings.py内容
'''
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'E:\CMS\aaa\db'
'''
import settings
import uuid
import pickle
import os
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
def save(self):
if not self.is_exists:
raise PermissionError('对象已存在')
file_path=r'%s%s%s' %(settings.DB_PATH,os.sep,self.id)
pickle.dump(self,open(file_path,'wb'))
@property
def is_exists(self):
tag=True
files=os.listdir(settings.DB_PATH)
for file in files:
file_abspath=r'%s%s%s' %(settings.DB_PATH,os.sep,file)
obj=pickle.load(open(file_abspath,'rb'))
if self.host == obj.host and self.port == obj.port:
tag=False
break
return tag
@staticmethod
def get_obj_by_id(id):
file_abspath = r'%s%s%s' % (settings.DB_PATH, os.sep, id)
return pickle.load(open(file_abspath,'rb'))
@staticmethod
def create_id():
return str(uuid.uuid1())
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
# print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.save()
conn1=MySQL('127.0.0.1',3306)
conn1.save() #抛出异常PermissionError: 对象已存在
obj=MySQL.get_obj_by_id('7e6c5ec0-7e9f-11e7-9acc-408d5c2f84ca')
print(obj.host)
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
t=time.localtime() #获取结构化的时间格式
return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
@staticmethod
def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
t=time.localtime(time.time()+86400)
return Date(t.tm_year,t.tm_mon,t.tm_mday)
a=Date('1987',11,27) #自己定义时间
b=Date.now() #采用当前时间
c=Date.tomorrow() #采用明天的时间
print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)
#分割线==============================
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now():
t=time.localtime()
return Date(t.tm_year,t.tm_mon,t.tm_mday)
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我们的意图是想触发EuroDate.__str__,但是结果为
'''
输出结果:
<__main__.Date object at 0x1013f9d68>
'''
因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__,解决方法就是用classmethod
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
# @staticmethod
# def now():
# t=time.localtime()
# return Date(t.tm_year,t.tm_mon,t.tm_mday)
@classmethod #改成类方法
def now(cls):
t=time.localtime()
return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪个类来调用,即用哪个类cls来实例化
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我们的意图是想触发EuroDate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿
'''
输出结果:
year:2017 month:3 day:3
'''
python中的oop常用的术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,name__及__doc
软件开发规范
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
面向对象
需求模型之5w1h8c
1. 需求VS功能
需求:客户想要的效果,对客户有价值的事情
功能:系统为了实现客户的价值而提供的能力/功能
举例:
汽车:驾驶是需求,刹车、加速、转弯是功能
打印机:打印是需求,进纸、设定、与电脑连接等是功能
pos机:买单是需求,商品扫描、金额汇总、收银等是功能
2. 需求的重要性
1/3的项目失败或陷入困境是因为需求原因导致的
garbage in,garbage out
屎上了生产饼干的流水线,最后产出的是像饼干一样的屎
修复需求错误的问题成本极高
1 编码阶段修复发现一个错误耗费人类是1个单位
2 测试阶段修复需求错误的成本是5-10倍
3 维护阶段(产品上线后),修复需求错误成本是20倍
ps:在需求阶段修复错误,成本只需要0.1-0.2即可
结论:需求错了,几乎要把软件项目重做一遍
3. 需求分析的目的
1 记录员,记录客户的需求
2 分析员,和客户一起分析,完善需求
3 引导员,能够引导客户的需求
4. 需求分析的方法
需求分析518方法,简称我要发,具体就是5w1h8c
5w:
when:用户想在什么时间用,例如半夜备份的任务,很明显我们得知该需求需要自动化执行
where:用户想在什么地方用,例如垃圾桶室内和室外的区别,同样的事物放到不同地方用肯定不一样
who:用户想让谁来用,不仅是人,也可以是一个系统
what:用户想要我们程序的输出结果是什么,如图片,文档,系统
why:问一问用户为什么要这么做,(你不问,他基本不说),包括客户所有觉得不爽的事情
ps:why是核心
1h:how
用例方法
8c:8个constraint约束
性能performance
性能是系统提供相应服务的效率。主要包括响应时间、吞吐量
性能是很多系统架构设计的关键约束条件之一
例如,同样一个web网站,虽然都是提供信息给用户流量,设计一个日访问量1w的网站与日访问量10亿的网站,二者的设计截然不同
成本cost
成本指为了实现系统而需要付出的代价
成本也是很多系统架构设计的关键约束之一
例如客户只愿意花100w,而我们却设计了一个耗费1000w的系统
时间time
指客户要求什么时候交付
可靠性reliability
指系统长时间正确运行的能力,银行、证券、电信这些公司,对宕机时间要求很严格
安全security
指对信息安全的保护能力,涉及到钱、身份证、社会保险号等需求对这个要求很高
合规性compliance
指满足各种行业标准、法律法规、规范等,例如3C、SOX、3GPP,ITUT
技术性technology
有的客户可能要求我们采用某种技术
例如客户现在都是windows服务器,要求我们基于windows平台开发
兼容性compatibility
指我们的产品与客户其他已有的产品或系统的兼容能力,要知道现在很少有产品是孤立运行的,特别是在大企业、大公司中,多个系统都是相互交互、互相配合的。新的系统必须能够和已有的系统配合,否则将无法运行
ps:
5w+1h属于功能属性
8c属于质量属性
需求模型之用例的写法
写用例的技巧
三段法:NEA
1 正常处理(normal):分析正常流程
2 异常处理(exception):分析每一步异常情况和对应的处理
3 替代处理(alternative):分析每一步是否有其他替代方法,以及如何做
用例的书写格式
#1. 用例名称
一般情况下,用例名称即需求名称
#2. 场景
场景即用例发生的环境,正好对应5w中的:when,where,who
#3. 用例描述
描述详细的用例内容,对应5w中的what和how
即用户应该怎样做,以及每个步骤中的输出,但不要求每个步骤都有一定的输出,可以有也可以没有,也可以有多个
#4. 用例价值
描述用例对应的客户价值,对应5w中的why
#5. 约束和限制
即真个需求流程中相关的约束和限制条件,对应518方法中的8C
用例编写案例
#用例名
答题系统
#场景:
when:8.10开始
where:老男孩
who:linux学院,网络客户
#用例描述:
linux学院提供50道题
每个客户无需输入任何个人信息就可以参与答题,随机选择20道题,给客户回答,每道题5分,
3.答题结束后,输入手机号,提交,算总分
60分参与抽奖,<60分赠送基础视频
#用户价值:
答题有奖,答题提交时输入自己的手机号获取成绩,获得潜在客户的联系方式,为后期将客户转成学员做准备
#约束:
暂无
领域模型
需求分析阶段不区分面向对象还是面向过程
领域模型是完成从需求分析到面向对象设计的一座桥梁
#定义:领域模型是对领域内的概念或现实世界中对象的可视化表示,又称为观念模型,领域对象模型,分析对象模型
#它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系
领域模型主要两个作用:
#1 发掘重要的业务领域概念
#2 建立业务领域概念之间的关系
归纳领域建模的方法就是:
#1 从用例中找名词(找完后需要删除不是领域对象的名词,具体删除什么,与不同领域有关,没有统一标准,靠经验)
#2 加属性(有些属性并没有在用例中明确给出,靠行业经验自己添加)
#3 连关系(画UML图)
设计模型
面向对象类设计的具体步骤
第一步:领域类映射(不是全盘拷贝)
类筛选:并不是每个领域类都会出现在软件中
名称映射:对应
属性映射:对应,照搬
提炼方法:领域类中并没有方法,在用例中找动词
第二步:应用设计原则和设计模式
第三步:拆分辅助类(领域类可以在实现阶段拆分为几个类)