一.預備知識 最近開始嘗試做一些tornado商城項目,在開始之前需要引入一些項目設計知識,如介面,抽象方法抽象類,組合,程式設計原則等,個人理解項目的合理設計可增加其靈活性,降低數據之間的耦合性,提高穩定性,下麵介紹一些預備知識 1.介面 其實py中沒有介面這個概念。要想實現介面的功能,可以通過主 ...
一.預備知識
最近開始嘗試做一些tornado商城項目,在開始之前需要引入一些項目設計知識,如介面,抽象方法抽象類,組合,程式設計原則等,個人理解項目的合理設計可增加其靈活性,降低數據之間的耦合性,提高穩定性,下麵介紹一些預備知識
1.介面
其實py中沒有介面這個概念。要想實現介面的功能,可以通過主動拋出異常來實現
介面作用:對派生類起到限制的作用
例:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 介面,python中的介面,通過在父類中主動拋出異常實現 介面的作用:起到了限制的作用 """ class IFoo: def fun1(self): pass raise Exception("----") class Bar(IFoo): def fun1(self): #方法名必須和父類中的方法名相同,不然沒辦法正常執行,會拋出異常 print("子類中如果想要調用父類中的方法,子類中必須要有父類中的方法名") def fun2(self): print("test") obj = Bar() obj.fun2()
2.抽象方法抽象類
抽象類,抽象方法是普通類和介面的綜合,即可以繼承也可以起到限製作用
由於python 本身沒有抽象類、介面的概念,所以要實現這種功能得abc.py 這個類庫,具體實現方法如下 :
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 抽象類,抽象方法 抽象類,抽象方法是普通類和介面的綜合,即可以繼承也可以起到限製作用 """ import abc class Foo(metaclass=abc.ABCMeta): def fun1(self): print("fun1") def fun2(self): print("fun2") @abc.abstractclassmethod def fun3(self): pass class Bar(Foo): def fun3(self): print("子類必須有父類的抽象方法名,不然會拋出異常") obj = Bar() obj.fun1() obj.fun2() obj.fun3()
3.組合
python中“多用組合少用繼承”,因為繼承的偶合性太強,可以把基類,當做參數傳入派生類中,用於解偶
如;
#!/usr/bin/env python # -*- coding: utf-8 -*- #繼承 class Animals: def eat(self): print(self.Name + " eat") def drink(self): print(self.Name + " drink") class Person(Animals): def __init__(self, name): self.Name = name def think(self): print(self.Name + " think") obj = Person("user1") obj.drink() obj.eat() obj.think()繼承
class Animals: def __init__(self,name): self.Name = name def eat(self): print(self.Name + " eat") def drink(self): print(self.Name + " drink") class Person: def __init__(self, obj): self.obj = obj def eat(self): self.obj.eat() def think(self,name): print(name + " think") animals = Animals("animals") obj = Person(animals) obj.think("person") obj.eat()組合
4.依賴註入
剛接觸理解的比較淺顯
像上一例中,如果有多層關係時,需要傳入多個對象,為瞭解決這個問題就引入了依賴註入,如上例在Person類實例化時自動傳入Animals對象
那麼,在引入依賴註入時先瞭解一下python類實例化過程中背後做了什麼事情
class Foo: def __init__(self): self.name = 111 def fun(self) print(self.name) obj = Foo() #obj是Foo的實例化對象
在python中一切皆對象,Foo是通過type類創建的
例:
#!/usr/bin/env python # -*- coding:utf-8 -*- class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls, *args, **kwargs) obj.__init__(*args, **kwargs) return obj class Foo(metaclass=MyType): def __init__(self, name): self.name = name def f1(self): print(self.name)
解釋器解釋: 1.遇到 class Foo,執行type的__init__方法 1.Type的init的方法里做什麼麽呢?不知道 obj = Foo(123) 3.執行Type的 __call__方法 執行Foo類的 __new__方法 執行Foo類的 __init__ 方法
先來瞭解幾個概念
new 和 __init()和__metaclass__:
-
__new__函數是實例一個類所要調用的函數,每當我們調用obj = Foo()來實例一個類時,都是先調用__new__()
-
然後再調用__init__()函數初始化實例. __init__()在__new__()執行後執行,
-
類中還有一個屬性 __metaclass__,其用來表示該類由 誰 來實例化創建,所以,我們可以為 __metaclass__ 設置一個type類的派生類,從而查看 類 創建的過程。
那麼依賴註入的實現方法,自定義一個type方法,實例化類的時候指定由自定義的type方法創建,具體實現方法如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- # 依賴註入應用 #DI class Mapper: __mapper_relation ={} @staticmethod def register(cls,value): Mapper.__mapper_relation[cls] = value @staticmethod def exist(cls): if cls in Mapper.__mapper_relation: return True return False @staticmethod def value(cls): return Mapper.__mapper_relation[cls] class MyType(type): def __call__(self, *args, **kwargs): obj = self.__new__(self, *args, **kwargs) arg_list = list(args) if Mapper.exist(self): value=Mapper.value(self) arg_list.append(value) obj.__init__(*arg_list, **kwargs) return obj #定義由誰來實例化 class Foo(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) class Bar(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) Mapper.register(Foo,"test1") Mapper.register(Bar,"test12") f=Foo() print(f.name)依賴註入應用
5.程式的設計原則
1. 單一責任原則一個對象只對一個元素負責
優點;
消除耦合,減小因需求變化引起代碼僵化
2.開放封閉原則
對擴展開放,對修改關閉
優點:
按照OCP原則設計出來的系統,降低了程式各部分之間的耦合性,其適應性、靈活性、穩定性都比較好。當已有軟體系統需要增加新的功能時,
不需要對作為系統基礎的抽象層進行修改,只需要在原有基礎上附加新的模塊就能實現所需要添加的功能。增加的新模塊對原有的模塊完全沒有影響或影響很小,
這樣就無須為原有模塊進行重新測試
如何實現 ?
在面向對象設計中,不允許更必的是系統的抽象層,面允許擴展的是系統的實現層,所以解決問題的關鍵是在於抽象化。
在面向對象編程中,通過抽象類及介面,規定具體類的特征作為抽象層,相對穩定,不需要做更改的從面可以滿足“對修改關閉”的原則;而從抽象類導出的具體 類可以
改變系統 的行為,從而滿足“對擴展開放的原則"
3.里氏替換原則
可以使用任何派生類替換基類 優點: 可以很容易的實現同一父類下各個子類的互換,而客戶端可以毫不察覺 4.介面分享原則 對於介面進行分類避免一個介面的方法過多,避免”胖介面" 優點: 會使一個軟體系統功能擴展時,修改的壓力不會傳到別的對象那裡 如何實現 ? 得用委托分離介面 利用多繼承分離介面5.依賴倒置原則
隔離關係,使用介面或抽象類代指 高層次的模塊不應該依賴於低層次的模塊,而是,都應該依賴於抽象 優點: 使用傳統過程化程式設計所創建的依賴關係,策略依賴於細節,這是糟糕的,因為策略受到細節改變的影響。 依賴倒置原則使細節和策略都依賴於抽象,抽象的穩定性決定了系統的穩定性 6.依賴註入和控制反轉原則 使用鉤子再原來執行流程中註入其他對象二.tornado項目設計實例
此實例只包含登錄,寫此實例目的在於更好的理解及應用以上的內容
1.目錄規劃
註:
Infrastructure 目錄:公共組件目錄
Model:業務邏輯處理目錄
Repository: 數據倉庫及數據處理目錄
Statics:靜態文件目錄如(css,js,images等)
UIAdmin: UI層
Views:模板文件目錄
Application.py : 服務啟動文件
2.業務訪問流程
介紹完目錄規劃,那就來講講業務訪問流程及數據走向
- 啟動服務後,客戶端訪問URL,根據tornado路由找到相對的handler進行處理
- 找到handler後其相對方法(get/post/delete/put)中調用Model邏輯處理層方法進行處理並接收處理結果
- Model邏輯處理層需
- 創建介面
- 建模
- 創建協調層
創建完之後 ,由協調層(這裡通用Services)調用數據層方法並接收處理結果返回給handler
4.數據處理層接收到Model調用後,處理數據並將數據返回給Model業務邏輯處理層
5.最終handler接收到最終結果,進行判斷處理,並將處理結果返回給用戶
3.具體實施
按照以上的訪問流程來看配置文件
1.啟動文件,路由關係配置
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from UIAdmin.Controllers import Account from UIAdmin.Controllers import Region from UIAdmin.Controllers import Customer from UIAdmin.Controllers import Merchant from UIAdmin import mapper settings = { 'template_path': 'Views', 'static_path': 'Statics', 'static_url_prefix': '/statics/', } application = tornado.web.Application([ (r"/login", Account.LoginHandler), (r"/check", Account.CheckCodeHandler), ],**settings) if __name__ == "__main__": application.listen(8000) tornado.ioloop.IOLoop.instance().start()Application.py
註:
settings 中指定配置,如模板文件路徑,靜態文件路徑等
application :路由配置,那個路徑由那個handler進行處理
2.handler配置
#!/usr/bin/env python # -*- coding: utf-8 -*- import io from Infrastructure.Core.HttpRequest import BaseRequestHandler from Infrastructure.utils import check_code from Model.User import UserService class LoginHandler(BaseRequestHandler): def get(self, *args, **kwargs): self.render("Admin/Account/login.html") def post(self, *args, **kwargs): username = self.get_argument("username",None) email = self.get_argument("email",None) pwd = self.get_argument("pwd",None) code = self.get_argument("checkcode",None) service = UserService() result = service.check_login(user=username,email=email,pwd=pwd) #obj封裝了所有的用戶信息,UserModel對象 if result and code.upper() == self.session["CheckCode"].upper(): self.session['username'] = result.username self.redirect("/ProvinceManager.html") else: self.write("alert('error')")hanler.py
handler中主要是針對數據訪問方式的不同,給出不同的處理方法,並將結果返回給客戶端
3.Model 邏輯處理層
邏輯處理層中,著重看的有三點- 建模
- 介面
- 協調
建模
#!/usr/bin/env python # -*- coding: utf-8 -*- #建模 from Infrastructure.DI.Meta import DIMetaClass class VipType: VIP_TYPE = ( {'nid': 1, 'caption': '銅牌'}, {'nid': 2, 'caption': '銀牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '鉑金'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in VipType.VIP_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption) class UserType: USER_TYPE = ( {'nid': 1, 'caption': '用戶'}, {'nid': 2, 'caption': '商戶'}, {'nid': 3, 'caption': '管理員'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in UserType.USER_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption) class UserModel: def __init__(self, nid, username,password, email, last_login, user_type_obj, vip_type_obj): self.nid = nid self.username = username self.email = email self.password = password self.last_login = last_login self.user_type_obj = user_type_obj self.vip_type_obj = vip_type_obj建模
介面
IUseRepository類:介面類,用於約束資料庫訪問類的方法
class IUserRepository: def fetch_one_by_user(self,user,pwd): """ 根據用戶名和密碼獲取對象 :param user: :param pwd: :return: """ def fetch_one_by_email(self, user, pwd): """ 根據郵箱和密碼獲取對象 :param user: :param pwd: :return: """介面
協調
協調作用主要是調用數據處理層的方法,並將數據處理層處理後的結果返回給它的上一層的調度者
class UserService(metaclass=DIMetaClass): def __init__(self, user_repository): """ :param user_repository: 數據倉庫對象 """ self.userRepository = user_repository def check_login(self,user,email,pwd): if user: #數據倉庫執行SQL後返回的字典 #{"nid":1,username:xxx,vip:2,usertype:1} ret = self.userRepository.fetch_one_by_user(user,pwd) else: ret = self.userRepository.fetch_one_by_email(email,pwd) return ret協調層UserService
4.Repository數據處理層
將處理後結果(usermodel對象)返回給上一層調度者(UserService)
#!/usr/bin/env python # -*- coding:utf-8 -*- #數據表創建 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqlalchemy import Integer, Integer, CHAR, VARCHAR, ForeignKey, Index, DateTime, DECIMAL, TEXT from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/ShoppingDb?charset=utf8", max_overflow=5) Base = declarative_base() class Province(Base): """ 省 """ __tablename__ = 'province' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) class City(Base): """ 市 """ __tablename__ = 'city' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) province_id = Column(Integer, ForeignKey('province.nid')) class County(Base): """ 縣(區) """ __tablename__ = 'county' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) city_id = Column(Integer, ForeignKey('city.nid')) class UserInfo(Base): """ 用戶信息 """ __tablename__ = 'userinfo' nid = Column(Integer, primary_key=True) USER_TYPE = ( {'nid': 1, 'caption': '用戶'}, {'nid': 2, 'caption': '商戶'}, {'nid': 3, 'caption': '管理員'}, ) user_type = Column(Integer) VIP_TYPE = ( {'nid': 1, 'caption': '銅牌'}, {'nid': 2, 'caption': '銀牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '鉑金'}, ) vip = Column(Integer) username = Column(VARCHAR(32)) password = Column(VARCHAR(64)) email = Column(VARCHAR(64)) last_login = Column(DateTime) ctime = Column(DateTime) __table_args__ = ( Index('ix_user_pwd', 'username', 'password'), Index('ix_email_pwd', 'email', 'password'), )SqlAchemyOrm.py
#!/usr/bin/env python # -*- coding: utf-8 -*- from Model.User import IUserRepository from Model.User import UserModel from Model.User import UserType from Model.User import VipType from Repository.Admin.DbConnection import DbConnection class UserRepository(IUserRepository): def __init__(self): self.db_conn = DbConnection() def fetch_one_by_email(self, email, password): ret = None cursor = self.db_conn.connect() sql = """select nid,username,password,email,last_login,vip,user_type from userinfo where email=%s and password=%s""" cursor.execute(sql, (email, password)) db_result = cursor.fetchone() self.db_conn.close() print(type(db_result), db_result) if db_result: ret = UserModel(nid=db_result['nid'], username=db_result['username'], password=db_result['password'], email=db_result['email'], last_login=db_result['last_login'], user_type_obj=UserType(nid=db_result['user_type']), vip_type_obj=VipType(nid=db_result['vip']),) return ret return db_result def fetch_one_by_user(self, username, password): ret = None cursor = self.db_conn.connect() sql = """select nid,username,password,email,last_login,vip,user_type from userinfo where username=%s and password=%s""" cursor.execute(sql, (username, password)) db_result = cursor.fetchone() self.db_conn.close() if db_result: #建模,將usermodel對象返回給上一層調用者,因為要向用戶展示的user_type不可能為1,2這些數據而應該是相對的caption ret = UserModel(nid=db_result['nid'], username=db_result['username'], password=db_result['password'], email=db_result['email'], last_login=db_result['last_login'], user_type_obj=UserType(nid=db_result['user_type']), vip_type_obj=VipType(nid=db_result['vip']),) return ret return db_result數據處理層
5.Handler最終處理
接收到最終處理結果後判斷,並返回數據給用戶
註:
有沒有註意到UserService是怎麼和數據處理層建立聯繫的?
這裡我們用到了依賴註入,具體配置如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- #依賴註入 class DIMapper: __mapper_dict = {} @staticmethod def inject(cls, arg): if cls not in DIMapper.__mapper_dict: DIMapper.__mapper_dict[cls] = arg @staticmethod def get_mappers(): return DIMapper.__mapper_dict