主頁 > 軟體設計 > 如何優化好UITableView,值得思考

如何優化好UITableView,值得思考

2022-09-17 09:36:29 軟體設計

如果你覺得 UITableViewDelegate 和 UITableViewDataSource 這兩個協議中有大量方法每次都是復制粘貼,實作起來大同小異;如果你覺得發起網路請求并決議資料需要一大段代碼,加上重繪和加載后簡直復雜度爆表,如果你想知道為什么下面的代碼可以滿足上述所有要求:

1

解耦后的VC

MVC

在討論解耦之前,我們要弄明白 MVC 的核心:控制器(以下簡稱 C)負責模型(以下簡稱 M)和視圖(以下簡稱 V)的互動,
這里所說的 M,通常不是一個單獨的類,很多情況下它是由多個類構成的一個層,最上層的通常是以 Model 結尾的類,它直接被 C 持有,Model 類還可以持有兩個物件:

  1. Item:它是實際存盤資料的物件,它可以理解為一個字典,和 V 中的屬性一一對應
  2. Cache:它可以快取自己的 Item(如果有很多)
    常見的誤區:
  3. 一般情況下資料的處理會放在 M 而不是 C(C 只做不能復用的事)
  4. 解耦不只是把一段代碼拿到外面去,而是關注是否能合并重復代碼, 并且有良好的拖展性,

原始版

在 C 中,我們創建 UITableView 物件,然后將它的資料源和代理設定為自己,也就是自己管理著 UI 邏輯和資料存取的邏輯,在這種架構下,主要存在這些問題:

  1. 違背 MVC 模式,現在是 V 持有 C 和 M,
  2. C 管理了全部邏輯,耦合太嚴重,
  3. 其實絕大多數 UI 相關都是由 Cell 而不是 UITableView 自身完成的,
    為了解決這些問題,我們首先弄明白,資料源和代理分別做了那些事,
    資料源
    它有兩個必須實作的代理方法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

簡單來說,只要實作了這個兩個方法,一個簡單的 UITableView 物件就算是完成了,
除此以外,它還負責管理 section 的數量,標題,某一個 cell 的編輯和移動等,
代理
代理主要涉及以下幾個方面的內容:

  1. cell、headerView 等展示前、后的回呼,
  2. cell、headerView 等的高度,點擊事件,
    最常用的也是兩個方法:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

提醒:絕大多數代理方法都有一個 indexPath 引數

優化資料源

最簡單的思路是單獨把資料源拿出來作為一個物件,
這種寫法有一定的解耦作用,同時可以有效減少 C 中的代碼量,然而總代碼量會上升,我們的目標是減少不必要的代碼,
比如獲取每一個 section 的行數,它的實作邏輯總是高度類似,然而由于資料源的具體實作方式不統一,所以每個資料源都要重新實作一遍,

SectionObject

首先我們來思考一個問題,資料源作為 M,它持有的 Item 長什么樣?答案是一個二維陣列,每個元素保存了一個 section 所需要的全部資訊,因此除了有自己的陣列(給cell用)外,還有 section 的標題等,我們把這樣的元素命名為 SectionObject:

@interface KtTableViewSectionObject : NSObject
@property (nonatomic, copy) NSString *headerTitle; // UITableDataSource 協議中的 titleForHeaderInSection 方法可能會用到
@property (nonatomic, copy) NSString *footerTitle; // UITableDataSource 協議中的 titleForFooterInSection 方法可能會用到
@property (nonatomic, retain) NSMutableArray *items;
- (instancetype)initWithItemArray:(NSMutableArray *)items;
@end

Item

其中的 items 陣列,應該存盤了每個 cell 所需要的 Item,考慮到 Cell 的特點,基類的 BaseItem 可以設計成這樣:

@interface KtTableViewBaseItem : NSObject
@property (nonatomic, retain) NSString *itemIdentifier;
@property (nonatomic, retain) UIImage *itemImage;
@property (nonatomic, retain) NSString *itemTitle;
@property (nonatomic, retain) NSString *itemSubtitle;
@property (nonatomic, retain) UIImage *itemAccessoryImage;
- (instancetype)initWithImage:(UIImage *)image Title:(NSString *)title SubTitle:(NSString *)subTitle AccessoryImage:(UIImage *)accessoryImage;
@end

父類實作代碼

規定好了統一的資料存盤格式以后,我們就可以考慮在基類中完成某些方法了,以 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 方法為例,它可以這樣實作:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (self.sections.count > section) {
        KtTableViewSectionObject *sectionObject = [self.sections objectAtIndex:section];
        return sectionObject.items.count;
    }
    return 0;
}

比較困難的是創建 cell,因為我們不知道 cell 的型別,自然也就無法呼叫 alloc 方法,除此以外,cell 除了創建,還需要設定 UI,這些都是資料源不應該做的事,
這兩個問題的解決方案如下:

  1. 定義一個協議,父類回傳基類 Cell,子類視情況回傳合適的型別,
  2. 為 Cell 添加一個 setObject 方法,用于決議 Item 并更新 UI,

優勢

經過這一番折騰,好處是相當明顯的:

  1. 子類的資料源只需要實作 cellClassForObject 方法即可,原來的資料源方法已經在父類中被統一實作了,
  2. 每一個 Cell 只要寫好自己的 setObject 方法,然后坐等自己被創建,被呼叫這個方法即可,
  3. 子類通過 objectForRowAtIndexPath 方法可以快速獲取 item,不用重寫,
    對照 demo(SHA-1:6475496),感受一下效果,

優化代理

我們以之前所說的,代理協議中常用的兩個方法為例,看看怎么進行優化與解耦,
首先是計算高度,這個邏輯并不一定在 C 完成,由于涉及到 UI,所以由 Cell 負責實作即可,而計算高度的依據就是 Object,所以我們給基類的 Cell 加上一個類方法:

+ (CGFloat)tableView:(UITableView*)tableView rowHeightForObject:(KtTableViewBaseItem *)object;

另外一類問題是以處理點擊事件為代表的代理方法, 它們的主要特點是都有 indexPath 引數用來表示位置,然而實際在處理程序中,我們并不關系位置,關心的是這個位置上的資料,
因此,我們對代理方法做一層封裝,使得 C 呼叫的方法中都是帶有資料引數的,因為這個資料物件可以從資料源拿到,所以我們需要能夠在代理方法中獲取到資料源物件,
為了實作這一點, 最好的辦法就是繼承 UITableView:

@protocol KtTableViewDelegate<UITableViewDelegate>
@optional
- (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath;
- (UIView *)headerViewForSectionObject:(KtTableViewSectionObject *)sectionObject atSection:(NSInteger)section;
// 將來可以有 cell 的編輯,交換,左滑等回呼
// 這個協議繼承了UITableViewDelegate ,所以自己做一層中轉,VC 依然需要實作某
@end
@interface KtBaseTableView : UITableView<UITableViewDelegate>
@property (nonatomic, assign) id<KtTableViewDataSource> ktDataSource;
@property (nonatomic, assign) id<KtTableViewDelegate> ktDelegate;
@end

cell 高度的實作如下,呼叫資料源的方法獲取到資料:

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
    id<KtTableViewDataSource> dataSource = (id<KtTableViewDataSource>)tableView.dataSource;
    KtTableViewBaseItem *object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
    Class cls = [dataSource tableView:tableView cellClassForObject:object];
    return [cls tableView:tableView rowHeightForObject:object];
}

優勢
通過對 UITableViewDelegate 的封裝(其實主要是通過 UITableView 完成),我們獲得了以下特性:

  1. C 不用關心 Cell 高度了,這個由每個 Cell 類自己負責
  2. 如果資料本身存在資料源中,那么在代理協議中它可以被傳給 C,免去了 C 重新訪問資料源的操作,
  3. 如果資料不存在于資料源,那么代理協議的方法會被正常轉發(因為自定義的代理協議繼承自 UITableViewDelegate)
    對照 demo(SHA-1:ca9b261),感受一下效果,

更加 MVC,更加簡潔

在上面的兩次封裝中,其實我們是把 UITableView 持有原生的代理和資料源,改成了 KtTableView 持有自定義的代理和資料源,并且默認實作了很多系統的方法,
到目前為止,看上去一切都已經完成了,然而實際上還是存在一些可以改進的地方:

  1. 目前仍然不是 MVC 模式!
  2. C 的邏輯和實作依然可以進一步簡化
    基于以上考慮, 我們實作一個 UIViewController 的子類,并且把資料源和代理封裝到 C 中,
@interface KtTableViewController : UIViewController<KtTableViewDelegate, KtTableViewControllerDelegate>
@property (nonatomic, strong) KtBaseTableView *tableView;
@property (nonatomic, strong) KtTableViewDataSource *dataSource;
@property (nonatomic, assign) UITableViewStyle tableViewStyle; // 用來創建 tableView
- (instancetype)initWithStyle:(UITableViewStyle)style;
@end

為了確保子類創建了資料源,我們把這個方法定義到協議里,并且定義為 required,

成果與目標

現在我們梳理一下經過改造的 TableView 該怎么用:

  1. 首先你需要創建一個繼承自 KtTableViewController 的視圖控制器,并且呼叫它的 initWithStyle 方法,
    objc KTMainViewController *mainVC = [[KTMainViewController alloc] initWithStyle:UITableViewStylePlain];
  2. 在子類 VC 中實作 createDataSource 方法,實作資料源的系結,
*   (void)createDataSource { self.dataSource = [[KtMainTableViewDataSource alloc] init]; // 這 一步創建了資料源 } ```

1.在資料源中,需要指定 cell 的型別,

*   (Class)tableView:(UITableView *)tableView cellClassForObject:(KtTableViewBaseItem *)object { return [KtMainTableViewCell class]; } 

1.在 Cell 中,需要通過決議資料,來更新 UI 并回傳自己的高度,

*   (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(KtTableViewBaseItem *)object { return 60; } // Demo 中沿用了父類的 setObject 方法, 

還有什么要優化的
到目前為止,我們實作了對 UITableView 以及相關協議、方法的封裝,使它更容易使用,避免了很多重復、無意義的代碼,
在使用時,我們需要創建一個控制器,一個資料源,一個自定義 Cell,它們正好是基于 MVC 模式的,因此,可以說在封裝與解耦方面,我們已經做的相當好了,即使再花大力氣,也很難有明顯的提高,
但關于 UITableView 的討論遠遠沒有結束,我列出了以下需要解決的問題

  1. 在這種設計下,資料的回傳不夠方便,比如 cell 的給 C 發訊息,
  2. 下拉重繪與上拉加載如何集成
  3. 網路請求的發起,與決議資料如何集成
    關于第一個問題,其實是普通的 MVC 模式中 V 和 C 的互動問題,可以在 Cell(或者其他類) 中添加 weak 屬性達到直接持有的目的,也可以定義協議,
    問題二和三是另一大塊話題,網路請求大家都會實作,但如何優雅的集成進框架,保證代碼的簡單和可拓展,就是一個值得深入思考,研究的問題了,接下來我們就重點討論網路請求,

為何創建網路層

一個 iOS 的網路層框架該如何設計?這是一個非常寬泛,也超出我能力范圍之外的問題,業內已有一些優秀的,成熟的思路和解決方案,由于能力,角色所限,我決定從一個普通開發者而不是架構師的角度來說說,一個普通的、簡單的網路層該如何設計,我相信再復雜的架構,也是由簡單的設計演化而來的,
對于絕大多數小型應用來說,集成 AFNetworking 這樣的網路請求框架就足以應付 99% 以上的需求了,但是隨著專案的擴大,或者用長遠的眼光來考慮,直接在 VC 中呼叫具體的網路框架(下面以 AFNetworking 為例),至少存在以下問題:

  1. 一旦日后 AFNetworking 停止維護,而且我們需要更換網路框架,這個成本將無法想象,所有的 VC 都要改動代碼,而且絕大多數改動都是雷同的,
    這樣的例子真實存在,比如我們的專案中就依然使用早已停止維護的 ASIHTTPRequest,可以預見,這個框架遲早要被替換,
  2. 現有的框架可能無法實作我們的需求,以 ASIHTTPRequest 為例,它的底層用 NSOperation 來表示每一個網路請求,眾所周知,一個 NSOperation 的取消,并不是簡單呼叫 cancel 方法就可以的,在不修改原始碼的前提下,一旦它被放入佇列,其實是無法取消的,
  3. 有時候我們的需求僅僅是進行網路請求,還會對這個請求進行各種自定義的拓展,比如我們可能要統計請求的發起和結束時間,從而計算網路請求,資料決議的步驟的耗時,有時候,我們希望設計一個通用組件,并且支持由各個業務部門去自定義具體的規則,比如可能不同的部門,會為 HTTP 請求添加不同的頭部,
  4. 網路請求還有可能有其他廣泛需要添加的需求,比如請求失敗時的彈窗,請求時的日志記錄等等,
    參考當前代碼(SHA-1:a55ef42)感受一下沒有任何網路層時的設計,

如何設計網路層

其實解決方案非常簡單:

所有的計算機問題,都可以通過添加中間層來解決

讀者可以自行思考,為什么添加中間層可以解決上述三個問題,

三大模塊

對于一個網路框架來說,我認為主要有三個方面值得去設計:

  1. 如何請求
  2. 如何回呼
  3. 資料決議

一個完整的網路請求一般由以上三個模塊組成,我們逐一分析每個模塊實作時的注意事項:

發起請求

發起請求時,一般有兩種思路,第一種是把所有要配置的引數寫到同一個方法中,借用 與時俱進,HTTP/2下的iOS網路層架構設計 一文中的代碼表示:

+ (void)networkTransferWithURLString:(NSString *)urlString
                       andParameters:(NSDictionary *)parameters
                              isPOST:(BOOL)isPost
                        transferType:(NETWORK_TRANSFER_TYPE)transferType
                   andSuccessHandler:(void (^)(id responseObject))successHandler
                   andFailureHandler:(void (^)(NSError *error))failureHandler {
                           // 封裝AFN
                   }

這種寫法的好處在于所有引數一目了然,而且簡單易用,每次都呼叫這個方法即可,但是缺點也很明顯,隨著引數和呼叫次數的增多,網路請求的代碼很快多到爆炸,
另一組方法則是將 API 設定成一個物件,把要傳入的引數作為這個物件的屬性,在發起請求時,只要設定好物件的相關屬性,然后呼叫一個簡單的方法即可,

@interface DRDBaseAPI : NSObject
@property (nonatomic, copy, nullable) NSString *baseUrl;
@property (nonatomic, copy, nullable) void (^apiCompletionHandler)(_Nonnull id responseObject,  NSError * _Nullable error);
- (void)start;
- (void)cancel;
...
@end

根據前文提到的 Model 和 Item 的概念,那么應該可以想到:這個用于訪問網路的 API 物件,其實是作為 Model 的一個屬性

Model 負責對外暴露必要的屬性和方法,而具體的網路請求則由 API 物件完成,同時 Model 也應該持有真正用來存盤資料的 Item,

如何回呼

一次網路請求的回傳結果應該是一個 JSON 格式的字串,通過系統的或者一些開源框架可以將它轉換成字典,

接下來我們需要使用 runtime 相關的方法,將字典轉換成 Item 物件,

最后,Model 需要將這個 Item 賦值給自己的屬性,從而完成整個網路請求,

如果從全域角度來說,我們還需要一個 Model 請求完成的回呼,這樣 VC 才能有機會做相應的處理,

考慮到 Block 和 Delegate 的優缺點,我們選擇用 Block 來完成回呼,

資料決議

這一部分主要是利用 runtime 將字典轉換成 Item,它的實作并不算難,但是如何隱藏好實作細節,使上層業務不用過多關心,則是我們需要考慮的問題,

我們可以定義一個基類的 Item,并且為它定義一個 parseData 函式:

// KtBaseItem.m
- (void)parseData:(NSDictionary *)data {
    // 決議 data 這個字典,為自己的屬性賦值
    // 具體的實作請見后面的文章
}

封裝 API 物件

首先,我們封裝一個 KtBaseServerAPI 物件,這個物件的主要目的有三個:

  1. 隔離具體的網路庫的實作細節,為上層提供一個穩定的的介面
  2. 可以自定義一些屬性,比如網路請求的狀態,回傳的資料等,方便的呼叫
  3. 處理一些公用的邏輯,比如網路耗時統計
    具體的實作請參考 Git 提交歷史:SHA-1:76487f7

Model 與 Item

BaseModel

Model 主要需要負責發起網路請求,并且處理回呼,來看一下基類的 Model 如何定義:

@interface KtBaseModel
// 請求回呼
@property (nonatomic, copy) KtModelBlock completionBlock;
//網路請求
@property (nonatomic,retain) KtBaseServerAPI *serverApi;
//網路請求引數
@property (nonatomic,retain) NSDictionary *params;
//請求地址 需要在子類init中初始化
@property (nonatomic,copy)   NSString *address;
//model快取
@property (retain,nonatomic) KtCache *ktCache;

它通過持有 API 物件完成網路請求,可以定制自己的存盤邏輯,控制請求方式的選擇(長、短鏈接,JSON或protobuf),
Model 應該對上層暴露一個非常簡單的呼叫介面,因為假設一個 Model 對應一個 URL,其實每次請求只需要設定好引數,就可以呼叫合適的方法發起請求了,
由于我們不能預知請求何時結束,所以需要設定請求完成時的回呼,這也需要作為 Model 的一個屬性,

BaseItem

基類的 Item 主要是負責 property name 到 json path 的映設,以及 json 資料的決議,最核心的字典轉模型實作如下:

- (void)parseData:(NSDictionary *)data {
    Class cls = [self class];
    while (cls != [KtBaseItem class]) {
        NSDictionary *propertyList = [[KtClassHelper sharedInstance] propertyList:cls];
        for (NSString *key in [propertyList allKeys]) {
            NSString *typeString = [propertyList objectForKey:key];
            NSString* path = [self.jsonDataMap objectForKey:key];
            id value = https://www.cnblogs.com/mysweetAngleBaby/archive/2022/09/16/[data objectAtPath:path];
            [self setfieldName:key fieldClassName:typeString value:value];
        }
        cls = class_getSuperclass(cls);
    }
}

完整代碼參考 Git 提交歷史:SHA-1:77c6392

如何使用

在實際使用時,首先要創建子類的 Modle 和 Item,子類的 Model 應該持有 Item 物件,并且在網路請求回呼時,將 API 中攜帶的 JSON 資料賦值給 Item 物件,
這個 JSON 轉物件的程序在基類的 Item 中實作,子類的 Item 在創建時,需要指定屬性名和 JSON 路徑之間的對應關系,
對于上層來說,它需要生成一個 Model 物件,設定好它的路徑以及回呼,這個回呼一般是網路請求回傳時 VC 的操作,比如呼叫 reloadData 方法,這時候的 VC 可以確定,網路請求的資料就存在 Model 持有的 Item 物件中,
具體代碼參考 Git 提交歷史:SHA-1:8981e28

下拉重繪

很多應用的 UITableview 都具有下拉重繪和上拉加載的功能,在實作這個功能時,我們主要考慮兩點:
1
隱藏底層的實作細節,對外暴露穩定易用的介面
2
Model 和 Item 如何實作
第一點已經是老生常談,參考 SHA-1 61ba974 就可以看到如何實作一個簡單的封裝,
重點在于對于 Model 和 Item 的改造,

ListItem

這個 Item 沒有什么別的作用,就是定義了一個屬性 pageNumber,這是需要與服務端協商的,Model 將會根據這個屬性這個屬性判斷有沒有全部加載完,

// In .h
@interface KtBaseListItem : KtBaseItem
@property (nonatomic, assign) int pageNumber;
@end
// In .m
- (id)initWithData:(NSDictionary *)data {
    if (self = [super initWithData:data]) {
        self.pageNumber = [[NSString stringWithFormat:@"%@", [data objectForKey:@"page_number"]] intValue];
    }
    return self;
}

對于 Server 來說,如果每次都回傳 page_number 無疑是非常低效的,因為每次引數都可能不同,計算總資料量是一項非常耗時的作業,因此在實際使用中,客戶端可以和 Server 約定,回傳的結果中帶有 isHasNext 欄位,通過這個欄位,我們一樣可以判斷是否加載到最后一頁,

ListModel

它持有一個 ListItem 物件, 對外暴露一組加載方法,并且定義了一個協議 KtBaseListModelProtocol,這個協議中的方法是請求結束后將要執行的方法,

@protocol KtBaseListModelProtocol <NSObject>
@required
- (void)refreshRequestDidSuccess;
- (void)loadRequestDidSuccess;
- (void)didLoadLastPage;
- (void)handleAfterRequestFinish; // 請求結束后的操作,重繪tableview或關閉影片等,
@optional
- (void)didLoadFirstPage;
@end
@interface KtBaseListModel : KtBaseModel
@property (nonatomic, strong) KtBaseListItem *listItem;
@property (nonatomic, weak) id<KtBaseListModelProtocol> delegate;
@property (nonatomic, assign) BOOL isRefresh; // 如果為是,表示重繪,否則為加載,
- (void)loadPage:(int)pageNumber;
- (void)loadNextPage;
- (void)loadPreviousPage;
@end

實際上,當 Server 端發生資料的增刪時,只傳 nextPage 這個引數是不能滿足要求的,兩次獲取的頁面并非完全沒有交集,很有可能他們具有重復元素,所以 Model 還應該肩負起去重的任務,為了簡化問題,這里就不完整實作了,

RefreshTableViewController

它實作了 ListMode 中定義的協議,提供了一些通用的方法,而具體的業務邏輯則由子類實作,

#pragma -mark KtBaseListModelProtocol
- (void)loadRequestDidSuccess {
    [self requestDidSuccess];
}
- (void)refreshRequestDidSuccess {
    [self.dataSource clearAllItems];
    [self requestDidSuccess];
}
- (void)handleAfterRequestFinish {
    [self.tableView stopRefreshingAnimation];
    [self.tableView reloadData];
}
- (void)didLoadLastPage {
    [self.tableView.mj_footer endRefreshingWithNoMoreData];
}
#pragma -mark KtTableViewDelegate
- (void)pullUpToRefreshAction {
    [self.listModel loadNextPage];
}
- (void)pullDownToRefreshAction {
    [self.listModel refresh];
}

實際使用

在一個 VC 中,它只需要繼承 RefreshTableViewController,然后實作 requestDidSuccess 方法即可,下面展示一下 VC 的完整代碼,它超乎尋常的簡單:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createModel];
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)createModel {
    self.listModel = [[KtMainTableModel alloc] initWithAddress:@"/mooclist.php"];
    self.listModel.delegate = self;
}
- (void)createDataSource {
    self.dataSource = [[KtMainTableViewDataSource alloc] init]; // 這一步創建了資料源
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
- (void)requestDidSuccess {
    for (KtMainTableBookItem *book in ((KtMainTableModel *)self.listModel).tableViewItem.books) {
        KtTableViewBaseItem *item = [[KtTableViewBaseItem alloc] init];
        item.itemTitle = book.bookTitle;
        [self.dataSource appendItem:item];
    }
}

其他的判斷,比如請求結束時關閉影片,最后一頁提示沒有更多資料,下拉重繪和上拉加載觸發的方法等公共邏輯已經被父類實作了,
具體代碼見 Git 提交歷史:SHA-1:0555db2
寫在結尾
網路請求的設計架構到此就全部結束了,它還有很多值的拓展的地方,還是那句老話,沒有通用的架構,只有最適合業務的架構,
我的 Demo 為了方便演示和閱讀,通常都是先實作底層的類和方法,然后再由上層呼叫,但實際上這種做法在實際開發中是不現實的,我們總是在發現大量冗余,無意義的代碼后,才開始設計架構,
因此在我看來,真正的架構程序是當業務發生變更(通常是變復雜了)時,我們開始應該思考當前哪些操作是可以省略的(由父類或代理實作),最上層應該以何種方式呼叫底層的服務,一旦設計好了最上層的呼叫方式,就可以逐步向底層實作了,
由于本人水平也有限,本文的架構并不優秀,希望在深入理解設計模式,積累更多經驗后,再與大家分享識訓,

青山不改,綠水常流,謝謝大家!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/508896.html

標籤:其他

上一篇:我的設計模式之旅、10 抽象工廠

下一篇:Java 并發編程決議 | 基于JDK原始碼決議Java領域中的并發鎖,我們可以從中學習到什么內容?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more