1 適配器模式的概念
2 數據直接適配帶來的困境
3 適配器模式實踐
4 適配器模式的優缺點
我們大家都知道,平時我們的生活用電,不同的國家,電壓是不同的,中國是220V,日本是100v,等等。那么我們使用的蘋果手機充電,不管在哪個國家,同一個充電器,都可以給手機來充電,那么這個充電器可以適配不同的電壓,那么這個充電器就相當于一個適配器。
1 適配器模式:將一個類的接口轉換成客戶希望的另外一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以在一起工作。
2 數據直接適配帶來的困境
a)直接賦值的弊端
b)用對象賦值的靈活性
c)如何降低數據層與視圖層的耦合度
實例講解:
1 直接賦值的弊端
我們假設在視圖頁面顯示一個明信片,明信片上面顯示 名字 一個分割線,一個日期,我們知道,不同的明信片 它的樣式是不一樣的,下面是一個明信片類
#import <UIKit/UIKit.h>
#define BUSINESS_FRAME CGRectMake(0, 0, 200, 130)
@interface BusinessCardView : UIView
/**
* 名字
*/
@property (nonatomic, strong) NSString *name;
/**
* 線條顏色
*/
@property (nonatomic, strong) UIColor *lineColor;
/**
* 電話號碼
*/
@property (nonatomic, strong) NSString *phoneNumber;
BusinessCardView.m
#import "BusinessCardView.h"
@interface BusinessCardView ()
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UIView *lineView;
@property (nonatomic, strong) UILabel *phoneNumberLabel;
@end
@implementation BusinessCardView
#pragma mark - 初始化
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
self.backgroundColor = [UIColor whiteColor];
self.layer.borderWidth = 0.5f;
self.layer.shadowOpacity = 0.5f;
self.layer.shadowOffset = CGSizeMake(5, 5);
self.layer.shadowRadius = 1.f;
self.layer.shadowColor = [UIColor grayColor].CGColor;
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 10, 150, 25)];
self.nameLabel.font = [UIFont fontWithName:@"Avenir-Light" size:20.f];
[self addSubview:self.nameLabel];
self.lineView = [[UIView alloc] initWithFrame:CGRectMake(0, 45, 200, 5)];
[self addSubview:self.lineView];
self.phoneNumberLabel = [[UILabel alloc] initWithFrame:CGRectMake(41, 105, 150, 20)];
self.phoneNumberLabel.textAlignment = NSTextAlignmentRight;
self.phoneNumberLabel.font = [UIFont fontWithName:@"AvenirNext-UltraLightItalic" size:16.f];
[self addSubview:self.phoneNumberLabel];
}
#pragma mark - 重寫setter,getter方法
@synthesize name = _name;
@synthesize lineColor = _lineColor;
@synthesize phoneNumber = _phoneNumber;
- (void)setName:(NSString *)name {
_name = name;
_nameLabel.text = name;
}
- (NSString *)name {
return _name;
}
- (void)setLineColor:(UIColor *)lineColor {
_lineColor = lineColor;
_lineView.backgroundColor = _lineColor;
}
- (UIColor *)lineColor {
return _lineColor;
}
- (void)setPhoneNumber:(NSString *)phoneNumber {
_phoneNumber = phoneNumber;
_phoneNumberLabel.text = phoneNumber;
}
- (NSString *)phoneNumber {
return _phoneNumber;
}
那么我們將這個明信片顯示在當前視圖上:
ViewController.m
#import "ViewController.h"
#import "BusinessCardView.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BusinessCardView *cardView = [[BusinessCardView alloc] initWithFrame:BUSINESS_FRAME];
cardView.center = self.view.center;
//直接賦值
cardView.name = @"JiKeXueYuan";
cardView.lineColor = [UIColor redColor];
cardView.phoneNumber = @"101 - 5687 - 000";
[self.view addSubview:cardView];
}
實際開發過程中,我們從服務器端接收的數據往往都是對象,或則列表數據,那么我們就不能直接給cardView.name = @"JiKeXueYuan"; 而可能會是cardView.name = someModel.name; 如果使用明信片的地方很多,那么我們這種直接賦值的地放都需要修改,大大增加了工作量。那么針對于這種問題,我們可以把數據封裝成一個對象,這樣或許能夠更方便管理一些。我們新建一個model類:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Model : NSObject
/**
* 名字
*/
@property (nonatomic, strong) NSString *name;
/**
* 線條顏色
*/
@property (nonatomic, strong) UIColor *lineColor;
/**
* 電話號碼
*/
@property (nonatomic, strong) NSString *phoneNumber;
@end
Model.m
#import "Model.h"
@implementation Model
@end
BusinessCardView.m 添加一個方法
-(void)loadData:(Model *)data
{
self.name = data.name;
self.phoneNumbr = data.phoneNumber;
self.lineColor = data.lineColor;
}
ViewController.m
#import "ViewController.h"
#import "BusinessCardView.h"
#import "Model.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BusinessCardView *cardView = [[BusinessCardView alloc] initWithFrame:BUSINESS_FRAME];
cardView.center = self.view.center;
// 以對象賦值
Model *model = [[Model alloc] init];
model.name = @"JiKeXueYuan";
model.lineColor = [UIColor redColor];
model.phoneNumber = @"101 - 5687 - 000";
[cardView loadData:model];
[self.view addSubview:cardView];
}
使用model減少了cardView 于當前要顯示的視圖的耦合度,但是又會出現一些靈活性的問題,cardView只能加載model類型的數據,現在假如我又有一個新的model NewModel, 那么cardView要顯示newModel的數據,那么只能新加一個方法,假如有還有第三個model 第四個model,那么工作量就大多了。這是直接賦值,和對象賦值的一些弊端,那么下面我們將采用適配器模式來解決這種問題。
3 使用適配器
a)創建抽象適配器對象
b)適配器與視圖層建立輸出聯系
c)適配器與數據層建立輸入聯系
適配器可以分為兩種,一種是類適配器,一種是對象適配器。
1 創建抽象適配器,OC中抽象類一般使用協議來實現。
BusinessCardAdapterProtocol.h
#import <Foundation/Foundation.h>
@protocol BusinessCardAdapterProtocol <NSObject>
- (NSString *)name;
- (UIColor *)lineColor;
- (NSString *)phoneNumber;
@end
接下來我們讓BusinessCardView 遵循這個協議
BusinessCardView.h
#import <UIKit/UIKit.h>
#import "BusinessCardAdapterProtocol.h"
#define BUSINESS_FRAME CGRectMake(0, 0, 200, 130)
@interface BusinessCardView : UIView
/**
* 名字
*/
@property (nonatomic, strong) NSString *name;
/**
* 線條顏色
*/
@property (nonatomic, strong) UIColor *lineColor;
/**
* 電話號碼
*/
@property (nonatomic, strong) NSString *phoneNumber;
/**
* 加載數據(實現了BusinessCardAdapterProtocol協議的數據)
*
* @param data 實現了BusinessCardAdapterProtocol協議的數據
*/
- (void)loadData:(id <BusinessCardAdapterProtocol>)data;
@end
BusinessCardView.m
#import "BusinessCardView.h"
@interface BusinessCardView ()
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UIView *lineView;
@property (nonatomic, strong) UILabel *phoneNumberLabel;
@end
@implementation BusinessCardView
#pragma mark - 初始化
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
self.backgroundColor = [UIColor whiteColor];
self.layer.borderWidth = 0.5f;
self.layer.shadowOpacity = 0.5f;
self.layer.shadowOffset = CGSizeMake(5, 5);
self.layer.shadowRadius = 1.f;
self.layer.shadowColor = [UIColor grayColor].CGColor;
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 10, 150, 25)];
self.nameLabel.font = [UIFont fontWithName:@"Avenir-Light" size:20.f];
[self addSubview:self.nameLabel];
self.lineView = [[UIView alloc] initWithFrame:CGRectMake(0, 45, 200, 5)];
[self addSubview:self.lineView];
self.phoneNumberLabel = [[UILabel alloc] initWithFrame:CGRectMake(41, 105, 150, 20)];
self.phoneNumberLabel.textAlignment = NSTextAlignmentRight;
self.phoneNumberLabel.font = [UIFont fontWithName:@"AvenirNext-UltraLightItalic" size:16.f];
[self addSubview:self.phoneNumberLabel];
}
- (void)loadData:(id <BusinessCardAdapterProtocol>)data {
self.name = [data name];
self.lineColor = [data lineColor];
self.phoneNumber = [data phoneNumber];
}
#pragma mark - 重寫setter,getter方法
@synthesize name = _name;
@synthesize lineColor = _lineColor;
@synthesize phoneNumber = _phoneNumber;
- (void)setName:(NSString *)name {
_name = name;
_nameLabel.text = name;
}
- (NSString *)name {
return _name;
}
- (void)setLineColor:(UIColor *)lineColor {
_lineColor = lineColor;
_lineView.backgroundColor = _lineColor;
}
- (UIColor *)lineColor {
return _lineColor;
}
- (void)setPhoneNumber:(NSString *)phoneNumber {
_phoneNumber = phoneNumber;
_phoneNumberLabel.text = phoneNumber;
}
- (NSString *)phoneNumber {
return _phoneNumber;
}
@end
創建一個抽象的適配器對象
BusinessCardAdapter.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "BusinessCardAdapterProtocol.h"
@interface BusinessCardAdapter : NSObject <BusinessCardAdapterProtocol>
/**
* 輸入對象
*/
@property (nonatomic, weak) id data;
/**
* 與輸入對象建立聯系
*
* @param data 輸入的對象
*
* @return 實例對象
*/
- (instancetype)initWithData:(id)data;
@end
BusinessCardAdapter.m
#import "BusinessCardAdapter.h"
@implementation BusinessCardAdapter
- (instancetype)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}
- (NSString *)name {
return nil;
}
- (UIColor *)lineColor {
return nil;
}
- (NSString *)phoneNumber {
return nil;
}
@end
創建一個model的適配器
ModelAdapter.h
#import "BusinessCardAdapter.h"
/**
* 類適配器
*/
@interface ModelAdapter : BusinessCardAdapter
@end
ModelAdapter.m
#import "ModelAdapter.h"
#import "Model.h"
@implementation ModelAdapter
- (instancetype)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}
- (NSString *)name {
Model *data = self.data;
return data.name;
}
- (UIColor *)lineColor {
Model *data = self.data;
return data.lineColor;
}
- (NSString *)phoneNumber {
Model *data = self.data;
return data.phoneNumber;
}
@end
讓我們再次展示這個cardView
ViewController.h
#import "ViewController.h"
#import "BusinessCardView.h"
#import "Model.h"
#import "ModelAdapter.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BusinessCardView *cardView = [[BusinessCardView alloc] initWithFrame:BUSINESS_FRAME];
cardView.center = self.view.center;
Model *cardModel = [[Model alloc] init];
cardModel.name = @"JiKeXueYuan";
cardModel.lineColor = @"black";
cardModel.phoneNumber = @"101 - 5687 - 000";
// 與輸入建立聯系
BusinessCardAdapter *modelAdapter = [[ModelAdapter alloc] initWithData:model];
// 與輸出建立聯系
[cardView loadData:modelAdapter];
[self.view addSubview:cardView];
}
@end
這樣我們整個適配器模式就已經完成了,下面假如我們又有一個新的model newModel要適配,那么我們就可以這樣做
NewCardModel.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface NewCardModel : NSObject
/**
* 名字
*/
@property (nonatomic, strong) NSString *name;
/**
* 線條顏色
*/
@property (nonatomic, strong) NSString *colorHexString;
/**
* 電話號碼
*/
@property (nonatomic, strong) NSString *phoneNumber;
@end
NewCardModel.m
#import "NewCardModel.h"
@implementation NewCardModel
@end
NewCardModelAdapter.h
#import "BusinessCardAdapter.h"
/**
* 類適配器
*/
@interface NewCardModelApater : BusinessCardAdapter
@end
NewCardModelAdapter.m
#import "NewCardModelApater.h"
#import "NewCardModel.h"
@implementation NewCardModelApater
- (instancetype)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}
- (NSString *)name {
NewCardModel *data = self.data;
return data.name;
}
- (UIColor *)lineColor {
NewCardModel *data = self.data;
// todo
if ([data.colorHexString isEqualToString:@"black"]) {
return [UIColor blackColor];
} else {
return [UIColor redColor];
}
}
- (NSString *)phoneNumber {
NewCardModel *data = self.data;
return data.phoneNumber;
}
@end
ViewController.m
#import "ViewController.h"
#import "BusinessCardView.h"
#import "NewCardModel.h"
#import "NewCardModelApater.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BusinessCardView *cardView = [[BusinessCardView alloc] initWithFrame:BUSINESS_FRAME];
cardView.center = self.view.center;
NewCardModel *newCardModel = [[NewCardModel alloc] init];
newCardModel.name = @"JiKeXueYuan";
newCardModel.colorHexString = @"black";
newCardModel.phoneNumber = @"101 - 5687 - 000";
// 與輸入建立聯系
BusinessCardAdapter *modelAdapter = [[NewCardModelApater alloc] initWithData:model];
// 與輸出建立聯系
[cardView loadData:modelAdapter];
[self.view addSubview:cardView];
}
@end
這樣我們就適配了一個新的model類,在此我們要介紹一下,類適配器,對象適配器。
像上面這樣,我們對于每個model類,都建立了一個相對應的adaper類,那么這種方式就是類類適配器,這樣也許每個model,我們都要建立也個adapter,有可能我們覺得會很麻煩,那么我們可不可以只使用一個適配器來適配所有的model呢,答案是可以的,下面我們建立一個CardAdapter 適配器來適配所有的model
CardAdapter.h
#import "BusinessCardAdapter.h"
/**
* 對象適配器
*/
@interface CardAdapter : BusinessCardAdapter
@end
CardAdapter.m
#import "CardAdapter.h"
#import "Model.h"
#import "NewCardModel.h"
@implementation CardAdapter
- (instancetype)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}
- (NSString *)name {
NSString *name = nil;
if ([self.data isMemberOfClass:[Model class]]) {
Model *model = self.data;
name = model.name;
} else if ([self.data isMemberOfClass:[NewCardModel class]]) {
NewCardModel *model = self.data;
name = model.name;
}
return name;
}
- (UIColor *)lineColor {
UIColor *lineColor = nil;
if ([self.data isMemberOfClass:[Model class]]) {
Model *model = self.data;
lineColor = model.lineColor;
} else if ([self.data isMemberOfClass:[NewCardModel class]]) {
NewCardModel *model = self.data;
if ([model.colorHexString isEqualToString:@"black"]) {
lineColor = [UIColor blackColor];
} else {
lineColor = [UIColor redColor];
}
}
return lineColor;
}
- (NSString *)phoneNumber {
NSString *phoneNumber = nil;
if ([self.data isMemberOfClass:[Model class]]) {
Model *model = self.data;
phoneNumber = model.phoneNumber;
} else if ([self.data isMemberOfClass:[NewCardModel class]]) {
NewCardModel *model = self.data;
phoneNumber = model.phoneNumber;
}
return phoneNumber;
}
@end
ViewController.m
#import "ViewController.h"
#import "BusinessCardView.h"
#import "Model.h"
#import "ModelAdapter.h"
#import "NewCardModel.h"
#import "NewCardModelApater.h"
#import "CardAdapter.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BusinessCardView *cardView = [[BusinessCardView alloc] initWithFrame:BUSINESS_FRAME];
cardView.center = self.view.center;
// 直接賦值
// cardView.name = @"JiKeXueYuan";
// cardView.lineColor = [UIColor redColor];
// cardView.phoneNumber = @"101 - 5687 - 000";
// 以對象賦值
Model *model = [[Model alloc] init];
model.name = @"JiKeXueYuan";
model.lineColor = [UIColor redColor];
model.phoneNumber = @"101 - 5687 - 000";
NewCardModel *newCardModel = [[NewCardModel alloc] init];
newCardModel.name = @"JiKeXueYuan";
newCardModel.colorHexString = @"black";
newCardModel.phoneNumber = @"101 - 5687 - 000";
// 與輸入建立聯系
BusinessCardAdapter *modelAdapter = [[CardAdapter alloc] initWithData:model];
// 與輸出建立聯系
[cardView loadData:modelAdapter];
[self.view addSubview:cardView];
}
@end
這種適配器就叫對象適配器,其實它跟類適配器差不多,只不過,它只是在一個適配器中,判斷了一下model的類型,然后處理不同的數據,實際應用中,至于使用類適配器,還是對象適配器,視情況而定,如果適配情況比較少,那么我們可以使用對象適配器,這樣可以減少適配器文件的數量,如果適配情況比較多,那么我們可以使用類適配器,這樣邏輯顯得比較清晰一下,如果還使用對象適配器的話,就會導致對象適配器判斷邏輯很多,對象龐大臃腫,今后不容易維護修改。
4 適配器模式的優缺點
優點:降低模塊耦合度,可以讓不修改現有模塊的情況下,從而實現新的功能
缺點:對于不懂適配器模式的人來說 可讀性比較差,java、C#等不支持多重繼承的語言,一次最多只能適配一個適配者類,而且目標抽象類只能為接口,不能為類,其使用有一定的局限性,不能將一個適配者類和他的子類同時適配到目標接口。
總結:實際使用中,我們什么時候使用適配器模式呢,一個簡單的視圖賦值,我們使用適配器模式需要寫那么多代碼,有時候我們覺得可能太麻煩,個人認為,如果我們在寫一些代碼給很多人用的時候,比如自己寫的開源框架,那么我們就要考慮使用適配器等這些設計模式,良好的模塊劃分,高度的可擴展性,別人用起來才會方便快捷。如果寫的代碼不給別人用,比如工作中的代碼實現,當人員就一兩個人的時候,那么為了能夠按時完成工作任務,那么這種簡單粗暴的賦值方式也未嘗不可。但是平時工作中,能使用設計模式有條件的情況下還是應該使用的,畢竟我們一直做代碼的搬運工,編程是門藝術,我們要去實現她的美。