Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MVPのようなものをチームに提案した話
Search
amacou
March 02, 2016
Programming
2.5k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
MVPのようなものをチームに提案した話
チームに開発効率化のためにMVPっぽいものを提案した話
amacou
March 02, 2016
More Decks by amacou
See All by amacou
LGTMについて
amacou
6
110k
Other Decks in Programming
See All in Programming
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
440
琵琶湖の水は止められてもNet--HTTPのリトライは止められない / You might be able to stop the water flow of Lake Biwa but you can't stop Net::HTTP retries
luccafort
PRO
0
620
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
160
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
0
1.7k
20260722_microCMSで考える、AI時代のコンテンツ運用設計
yosh1
0
380
TSX の <Hoge<Fuga>> という構文に驚いた話 / tsx-type-argument-syntax
kanaru0928
0
210
VibeCodingからAgenticWorkflowへ
starfish719
0
330
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
300
言葉の格闘技のススメ~紙とペンと言葉から始める、キャリアの描き方~
progresscicada
2
140
数百円から始めるRuby電子工作
tarosay
0
140
AI Engineeringは、AIプロダクトだけのものか? 〜AIがソフトウェアを作る時代の新しい当たり前〜 / No AI in your product. AI Engineering in your development.
rkaga
4
380
今さら聞けない .NET CLI
htkym
0
180
Featured
See All Featured
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
230
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
680
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
260
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.7k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
Being A Developer After 40
akosma
91
590k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
240
Embracing the Ebb and Flow
colly
88
5.1k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
340
Google's AI Overviews - The New Search
badams
0
1.1k
Transcript
MVPͷΑ͏ͳͷΛ νʔϜʹఏҊͨ͠ shibuya.swift #3 2016/03/01 @amacou
ࣗݾհ • ఱ ળ(Amano Yoshihiko) • @amacou • GMOϖύϘ minneࣄۀ෦
• ruby Rails Objective-c swift
͍ͬͯΔ͜ͱ • ೖ͔ࣾͯ͠ΒminneͷαʔόαΠυ(Rails)ΛҰ ؒ • ͦͷޙiOSΞϓϦҰؒObjective-c(ͱͪΐͬ ͱ͚ͩSwift) • جຊతʹදʹग़ͯ͜ͳ͍ͱ͜ΖͷվળΛ୲
ࠓ͢͜ͱ • ෳࡶͳը໘ͷΞϓϦͩͱMVCͰͭΒ͘ͳ͍ Ͱ͔͢ͱ͍͏ • ͦΕͬͯMVP͑࠷ݶͷมߋͰ͋Δఔ ͏·͍͘Μ͡ΌͶ͍ͬͯ͏
ෳࡶͳը໘ͷΞϓϦͰͭΒ͍ ͜ͱ
ෳࡶͳը໘ͱ • ୯७Ͱͳ͍ը໘ • ෳͷίϯϙʔωϯτ͕ஔͯ͋͠Δ • Ұͭͷը໘ʹෳͷTableView CollectionViewΛͬͯͨΓ͢Δ
ෳࡶͳը໘ͷΞϓϦͰͳʹ͕ͭ Β͍͔ • ίʔυϨϏϡʔ͕ͭΒ͍ • ςετ͕ͭΒ͍
ίʔυϨϏϡʔ͕ͭΒ͍
ෳࡶͳը໘ͷϨϏϡʔͷͭΒ͞ • ViewControllerͷߦ͕ଟ͘ͳΓ͕ͪ • ϨϏϡʔ࣌ʹ֬ೝ͠ͳ͚Ε͍͚ͳ͍ൣғ͕͕Δ • मਖ਼ʹ͕͔͔࣌ؒΔ • ࠶ϨϏϡʔ͔࣌ͬ͢ΓΕͯ·ͨ࠷ॳ͔Βݟ͠ •
ͭΒ͍
ςετ͕ͭΒ͍
ςετͷͭΒ͞ • ςετΤϯδχΞͷᅂΈ • ͔͠͠ෳࡶͳViewControllerͷUnitςετͭ Β͍ • UIςετʹཔΓ͕ͪ
ෳࡶͳViewControllerͷUnitς ετͷͭΒ͞ • ViewͱViewControllerབྷΈ߹͍ͬͯΔ͜ͱ͕ଟ͍ • ෳࡶͳ࣮ͱෳࡶͳςετηοτ • ෳࡶͳ࣮Λςετ͢ΔͱΕͳ͘ෳࡶͳςετʹͳΔ • ෳࡶͳςετอकͰ͖ͳ͍
• UIςετʹཔΓ͕ͪʹͳΔ
UIςετͷͭΒ͞ • ςετΛॻ͘ͷʹ͕͔͔࣌ؒΔ • ࣮ߦ͕͔͔࣌ؒ͘͢͝Δ • ࡉ͔͍ذͷςετ͕ͩΔ͘ͳΔ • ςετΛ૿͠ʹ͍͘ •
ςετͷอकʹ͕͔͔࣌ؒΔ • յΕ͍͢ • ͬͺΓUnitςετΛްͨ͘͘͠ͳΔ
ͳͥ͜ΜͳʹͭΒ͍ͷ͔
ͳͥ͜ΜͳʹͭΒ͍ͷ͔ • Unitςετ͕ͭΒ͍ͷɺϨϏϡʔ͕ͭΒ͍ͷෳ ࡶͳViewControllerݪҼ • ViewControllerʹɺؔ৺ࣄ͕ूத͍͗ͯ͢͠Δ • γϯϓϧʹ͢Δʹɺؔ৺ࣄͰׂ͢Ε͍͍ • =>
MVPͬΆ͘͢Ε͍͍͡ΌΜͯͳͬͨ
MVPͬΆ͘͢Δͱ
MVPͱ • Model-View-Presenterͷུ • Observer SynchronizationํࣜͱPassive View ํࣜͷ2ͭํ͕ࣜ͋Δ
MVP (Observer Synchronization) • Model • MVCͷModelͱಉ͡ • View •
UIΛ୲͠ɺModelΛObserverͰࢹͯ͠ɺModelͷมߋΛ Viewʹөͤ͞Δ • Presenter • View͔ΒͷΠϕϯτΛड͚औͬͯɺModelΛߋ৽͢Δ
MVP(Passive View) • Model • MVCͷModelͱಉ͡ • View • MVCͷView
• ModelΛ͞ΘΒͳ͍ • ViewΛߋ৽͢ΔͨΊͷInterfeceΛ࣋ͭ • Presenter • View͔ΒͷΠϕϯτΛड͚औͬͯɺModelΛߋ৽͢Δ • ඞཁʹԠͯ͡ViewΛInterfaceΛ௨ͯ͡ߋ৽͢Δ
ࠓճѻ͏MVP • ࠓճѻ͏MVPPassive Viewํࣜ • ैདྷͷMVCͱͷੑ͕ߴ͍ͨΊ • ͳΔ͘ViewΛബ͓͖͍ͯͨͨ͘͠Ί • =>
ࠓճMVPͱݴͬͨΒPassive Viewํ ࣜͩͱࢥ͍ͬͯͩ͘͞
MVCͱMVPͷҧ͍
MVC 7JFX View ViewController Model
MVP View/VC Presenter 7JFX Model
͋Μ·ΓҧΘͳ͍?
͋Μ·ΓҧΘͳ͍ʂ • جຊతͳҧ͍2ͭͷখ͞ͳଋ͚ͩʂ • View͔ΒModelΛ৮Βͳ͍ • View <=> PresenterϝιουΛ༻ҙͯ͠ ૢ࡞͢Δ
͡Ό͋རʁ
MVC View ViewController Model
MVP 7JFX 7JFX 7JFX$POUSPMMFS 7JFX View/VC Presenter Model
େ͖ͳར • MVPখ͞ͳView୯ҐͰPresenterΛׂΓͯΒΕΔ • ؔ৺ࣄΛখ͘͞Ͱ͖Δ = ίʔυΛίϯύΫτ/γϯϓϧʹͰ͖Δ • ςετγϯϓϧʹͰ͖Δ •
=> Unitςετ͍͢͠ʂ • Presenter͍·Θ͍͢͠ʂʂ • ॏෳ࣮ͨ͠͠ͳͯ͘ࡁΉ
ͰͦΕࠓྲྀߦΓͷΠέͯ ΔΞʔΩςΫνϟͰಉͩ͡ ΑͶʁ
ͳͥଞͷΞʔΩςΫνϟͰ ͳ͘MVPͳͷ͔
ଞͷΞʔΩςΫνϟ • MVVM(Model View ViewModel)ͱ͔DDD(Domain-Driven Design)ͱ͔ • ొਓ͕ଟ͍(Domain Model/Usecase/Input Port/
Output Port/Interface Adapter/Controller/Presenter/ Gateway) • νʔϜશһʹจԽΛਁಁͤ͞Δͷ݁ߏେม • DataBindingศར͚ͩͲɺࠓղܾ͍ͨ͜͠ͱ͡Όͳ͍
MVPͷར • MVC͔ΒͷୈҰาʹ࠷ద • ͦͷޙMVVMDDDͳͲͷൃలͰ͖Δ • ΏΔ;ΘͰ͋Γͳ͕Βͦͦ͜͜γϯϓϧʹͳ Γͦ͏
࣮ํ๏
͍͍ͩͨ͜Μͳը໘
None
AMNTopViewController.m #import "AMNTopViewController.h" @interface AMNTopViewController () @end @implementation AMNTopViewController -
(void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
AMNTimelineTableViewPresenter.m @interface AMNTimelineTableViewPresenter() @property (nonatomic) AMNTimeline *timeline; @end @implementation AMNTimelineTableViewPresenter
- (instancetype)init { if (self = [super init]) { self.timeline = [AMNTimeline new]; } return self; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.timeline.posts.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id post = self.timeline.posts[indexPath.row]; NSString *identifier = [NSString stringWithFormat:@"%@Cell", NSStringFromClass([post class])]; id cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if ([cell respondsToSelector:@selector(presenter)]) { NSObject<AMNPresenterProtocol> *presenter = [cell presenter]; [presenter presentWithModel:post viewController:self.viewController]; } return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 160.f; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { id post = self.timeline.posts[indexPath.row]; if ([post isKindOfClass:[AMNPhoto class]]) { AMNImageViewController *imageViewController = [[self.viewController storyboard] instantiateViewControllerWithIdentifier: NSStringFromClass([AMNImageViewController class])]; imageViewController.presenter.photo = post; [[self.viewController navigationController] pushViewController:imageViewController animated:YES]; } else if ([post isKindOfClass:[AMNText class]]) { AMNTextViewController *textViewController = [[self.viewController storyboard] instantiateViewControllerWithIdentifier: NSStringFromClass([AMNTextViewController class])]; textViewController.presenter.text = post; [[self.viewController navigationController] pushViewController:textViewController animated:YES]; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @end
None
AMNPhotoTableViewCell/ AMNPhotoPresenter @interface AMNPhotoTableViewCell() @property (nonatomic) IBOutlet UIImageView *photoImageView; @end
@implementation AMNPhotoTableViewCell - (void)awakeFromNib { self.presenter = [[AMNPhotoPresenter alloc] initWithView:self]; } - (void)setImage:(UIImage *)image { self.photoImageView.image = image; } @end @implementation AMNPhotoPresenter - (instancetype)initWithView: (UIView<AMNPhotoPresenterProtocol> *)view { if (self = [self init]) { self.view = view; } return self; } - (void)presentWithModel:(AMNPhoto *)model viewController:(UIViewController *)viewController { self.photo = model; self.viewController = viewController; [self reloadData]; } - (void)reloadData { [self.view setImage:self.photo.image]; } @end
None
AMNTextTableViewCell/ AMNTextPresenter @interface AMNTextTableViewCell() @property (nonatomic) IBOutlet UILabel *label; @end
@implementation AMNTextTableViewCell - (void)awakeFromNib { self.presenter = [[AMNTextPresenter alloc] initWithView:self]; } - (void)setText:(NSString *)text { self.label.text = text; } @end @implementation AMNTextPresenter - (instancetype)initWithView: (UIView<AMNTextPresenterProtocol> *)view { if (self = [self init]) { self.view = view; } return self; } - (void)presentWithModel:(AMNText *)model viewController:(UIViewController *)viewController { self.text = model; self.viewController = viewController; [self reloadData]; } - (void)reloadData { [self.view setText:self.text.text]; } @end
None
AMNTextTableViewCell/ AMNTextPresenter @interface AMNPhotoListTableViewCell() @property (nonatomic) IBOutlet UICollectionView *collectionView; @end
@implementation AMNPhotoListTableViewCell - (void)awakeFromNib { self.presenter = [[AMNPhotoListCollectionViewPresenter alloc] initWithView:self.collectionView]; } @implementation AMNPhotoListCollectionViewPresenter - (instancetype)initWithView:(UICollectionView *)view { if (self = [super init]) { self.view = view; self.view.delegate = self; self.view.dataSource = self; } return self; } - (void)presentWithModel:(AMNPhotoList *)model viewController:(UIViewController *)viewController { self.photoList = model; self.viewController = viewController; [self reloadData]; } - (void)reloadData { [self.view reloadData]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.photoList.list.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { AMNPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AMNPhotoCollectionViewCell" forIndexPath:indexPath]; cell.presenter.photo = self.photoList.list[indexPath.row]; [cell.presenter reloadData]; return cell; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return CGSizeMake(CGRectGetHeight(self.view.bounds), CGRectGetHeight(self.view.bounds)); } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { id post = self.photoList.list[indexPath.row]; if ([post isKindOfClass:[AMNPhoto class]]) { AMNImageViewController *imageViewController = [[self.viewController storyboard] instantiateViewControllerWithIdentifier: NSStringFromClass([AMNImageViewController class])]; imageViewController.presenter.photo = post; [[self.viewController navigationController] pushViewController:imageViewController animated:YES]; } [collectionView deselectItemAtIndexPath:indexPath animated:YES]; } @end
None
AMNPhotoCollectionViewCell @interface AMNPhotoCollectionViewCell() @property (nonatomic, weak) IBOutlet UIImageView *imageView; @end
@implementation AMNPhotoCollectionViewCell - (void)awakeFromNib { self.presenter = [[AMNPhotoPresenter alloc] initWithView:self]; } - (void)setImage:(UIImage *)image { self.imageView.image = image; } @end
࣮ํ๏ • ଓ͖GithubͰ • https://github.com/amacou/MVPExample
·ͱΊ • ͠ɺViewController͕ෳࡶʹͳͬͯେมʹ ͳ͖ͬͯͨΒׂͯ͠ΈΔͱΘΓͱ͍͍͔ • ͦͷͱ͖࠷ݶͷมߋͰՄೳͳΞʔΩςΫνϟ ͱͯ͠MVP͕͋ΔΑ
͋Γ͕ͱ͏͍͟͝·ͨ͠