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
Zenject Example ~さよならManager編~
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
いも
August 26, 2019
Programming
1.9k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Zenject Example ~さよならManager編~
Roppongi.unity #04 で発表した資料です
いも
August 26, 2019
More Decks by いも
See All by いも
UnityプログラミングバイブルR6号宣伝&Unity Logging小話
adarapata
0
640
Unityテスト活動のふりかえり
adarapata
1
670
Gather.townはいいぞ その後
adarapata
1
1.7k
Unityでの開発事例
adarapata
3
23k
どこのご家庭にもあるシーンマネージャーの話
adarapata
2
8.8k
Gather.townはいいぞ
adarapata
2
2.4k
宴はいいぞ
adarapata
0
2.2k
わかった気になるモブプログラミング
adarapata
1
180
モブワークっぽいのをやっている話/Trying mobwork
adarapata
2
1.3k
Other Decks in Programming
See All in Programming
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
14
6.8k
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
1
160
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
AI時代の仕事技芸論〜ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ(スクフェス仙台 2026バージョン)
kuranuki
0
610
OS アップデート対応の取り組み方がもっと共有されてほしい
andpad
0
110
アルゴリズムは何を圧縮しているのか ─ Haskell から育った「圧縮代数」というメンタルモデル
naoya
16
3.3k
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
350
技術記事、 専門家としてのプログラマ、 言語化
mizchi
14
7.4k
SREの積み重ねがAI駆動開発のガードレールになった ― 7つの実践/SRE Guardrails The 7
tomoyakitaura
8
3.9k
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
280
Webフレームワークの ベンチマークについて
yusukebe
0
200
AI駆動開発を妨げる技術的負債の解消アプローチ / ai-refactoring-approach
minodriven
17
8.9k
Featured
See All Featured
The Limits of Empathy - UXLibs8
cassininazir
1
440
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
390
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.7k
Color Theory Basics | Prateek | Gurzu
gurzu
0
390
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
150
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
220
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
230
Optimising Largest Contentful Paint
csswizardry
37
3.8k
WENDY [Excerpt]
tessaabrams
11
38k
Transcript
Zenject Example ~さよならManager ~ いも(@adarapata) 2019/08/26 Roppongi.unity #04 1
今日話すこと Manager をZenject 使って滅ぼす話 Zenject を多少知ってる人向け 2019/08/26 Roppongi.unity #04 2
どこのご家庭にもあるMasterDataManager public class MasterDataManager : SingletonMonoBehaviour<MasterDataManager> { public List<PlayerData> Players
{ get; private set; } public List<EnemyData> Enemies { get; private set; } public List<ItemData> Items { get; private set; } private CsvLoader _provider; public PlayerData FindById(int id) => Players.Find(p => p.id == id); void Start() { Players = _provider.LoadAll<PlayerData>("path/to/players"); Enemies = _provider.LoadAll<EnemyData>("path/to/enemies"); Items = _provider.LoadAll<ItemData>("path/to/items"); } } 2019/08/26 Roppongi.unity #04 3
MasterDataManager の気になるポイント 責務が多い インスタンスを持つだけでなく便利メソッド生え始めた 今後あらゆるデータが追加されて肥大する香りがする テスタビリティが低い 1 メソッドテストするのに複数の要素が必要になる static なのでMasterDataManager
に依存しているコード側もテス トしにくい 2019/08/26 Roppongi.unity #04 4
MasterDataManaer の立ち位置を考える なぜMasterDataManager という名前がついてたのか? 複数の種類のデータクラスを纏めて持っているから なぜ複数の種類のデータを持ってたのか? 全部ユニークなインスタンスなのでSingleton なインスタンスに持 たせた方が楽 なぜSingleton
なのか いろんなところから簡単にアクセスできるようにしたい 各依存先に渡すのが大変だから これらを解決できるならばManager は不要になる 2019/08/26 Roppongi.unity #04 5
やっていくぞ 2019/08/26 Roppongi.unity #04 6
まずはテスト namespace Tests { public class MasterDataManagerTest { private MasterDataManager
_manager; [UnityTest] public IEnumerator FindById_Success() { _manager = new GameObject().AddComponent<MasterDataManager>(); yield return new WaitForEndOfFrame(); var playerData = _manager.FindById(1); Assert.AreEqual("Foo", playerData.name); } } } 2019/08/26 Roppongi.unity #04 7
コードのお気持ちを探る なぜSingleton なの? ← イマココ なぜMonoBehaviour なの? 2019/08/26 Roppongi.unity #04
8
なぜSingleton なの? いろんなところから簡単にアクセスできるようにしたい 各依存先に渡すのが大変だから これを解決できるならばSingleton である必要はない。 じゃあDI しましょう。 2019/08/26 Roppongi.unity
#04 9
MasterDataManager をBind する まだMonoBehaviour なのでZenject Binding でサッとやる 2019/08/26 Roppongi.unity #04
10
Project Context を作って子にする 2019/08/26 Roppongi.unity #04 11
呼ぶ側が書き直せる public class FooBehaviour { public void Foo(){ var player
= MasterDataManager.Instance.FindById(1); } } ↓ public class FooBehaviour { [Inject] private MasterDataManager manager; public void Foo(){ var player = manager.FindById(1); } } 2019/08/26 Roppongi.unity #04 12
全箇所移行完了したらMonoBehaviour に書き直す public class MasterDataManager : MonoBehaviour { public List<PlayerData>
Players { get; private set; } public List<EnemyData> Enemies { get; private set; } public List<ItemData> Items { get; private set; } private CsvLoader _provider; public PlayerData FindById(int id) => Players.Find(p => p.id == id); void Start() { Players = _provider.LoadAll<PlayerData>("path/to/players"); Enemies = _provider.LoadAll<EnemyData>("path/to/enemies"); Items = _provider.LoadAll<ItemData>("path/to/items"); } } 2019/08/26 Roppongi.unity #04 13
コードのお気持ちを探る なぜSingleton なの? :done: なぜMonoBehaviour なの? ← イマココ 2019/08/26 Roppongi.unity
#04 14
なぜMonoBehaviour なの? 初期化処理( ロード部分) を呼びたい 誰も初期化を呼ぶ人がいないのでStart() を使いたい これを解決できるならばMonoBehaviour である必要はない。 2019/08/26
Roppongi.unity #04 15
IInitializable を実装してみよう public class MasterDataManager : MonoBehaviour, IInitializable { public
List<PlayerData> Players { get; private set; } public List<EnemyData> Enemies { get; private set; } public List<ItemData> Items { get; private set; } private CsvLoader _provider; public PlayerData FindById(int id) => Players.Find(p => p.id == id); public void Initialize(){ Players = _provider.LoadAll<PlayerData>("path/to/players"); Enemies = _provider.LoadAll<EnemyData>("path/to/enemies"); Items = _provider.LoadAll<ItemData>("path/to/items"); } } 2019/08/26 Roppongi.unity #04 16
IInitializable Zenject が提供するinterface やることは初期化のみ 実装すると、依存関係解決後にInitialize() をコールしてくれる 裏側でInitializableManager が動いてる。 2019/08/26 Roppongi.unity
#04 17
不要になったのでMonoBehaviour をやめよう public class MasterDataManager : IInitializable { public List<PlayerData>
Players { get; private set; } public List<EnemyData> Enemies { get; private set; } public List<ItemData> Items { get; private set; } private CsvLoader _provider; public PlayerData FindById(int id) => Players.Find(p => p.id == id); public void Initialize(){ Players = _provider.LoadAll<PlayerData>("path/to/players"); Enemies = _provider.LoadAll<EnemyData>("path/to/enemies"); Items = _provider.LoadAll<ItemData>("path/to/items"); } } 2019/08/26 Roppongi.unity #04 18
MonoBehaviour じゃなくなったのでInstaller を書こう public class MasterDataInstaller : MonoInstaller { public
override void InstallBindings() { Container.Bind<MasterDataManager>().AsSingle(); } } 2019/08/26 Roppongi.unity #04 19
テストも少し書き直そう namespace Tests { public class MasterDataManagerTest { private MasterDataManager
_manager; // MonoBehaviour じゃないのでEditTest で十分 [Test] public void FindById_Success() { _manager = new MasterDataManager(); _manager.Initialize(); var playerData = _manager.FindById(1); Assert.AreEqual("Foo", playerData.name); } } } 2019/08/26 Roppongi.unity #04 20
今一度MasterDataManaer の立ち位置を考える なぜMasterDataManager という名前がついてたのか? 複数の種類のデータクラスを纏めて持っているから なぜ複数の種類のデータを持ってたのか? 全部ユニークなインスタンスなのでSingleton なインスタンスに持 たせた方が楽 なぜSingleton
なのか いろんなところから簡単にアクセスできるようにしたい 各依存先に渡すのが大変だから 2019/08/26 Roppongi.unity #04 21
今一度MasterDataManaer の立ち位置を考える なぜSingleton なのか => DI でOK なぜ複数の種類のデータを持ってたのか? => 直接Inject
できるなら 纏める必要はない なぜMasterDataManager という名前がついてたのか? => 纏める必 要ないなら不要では? 2019/08/26 Roppongi.unity #04 22
データ持ってるクラスを独立 public class PlayerDataRepository { private readonly List<PlayerData> _resources; public
PlayerDataRepository(IResourceLoadProvider _provider) { _resources = _provider.LoadAll<PlayerData>("path/to/players"); } public PlayerData FindById(int id) => _resources.Find(p => p.id == id); } public class MasterDataInstaller : MonoInstaller { public override void InstallBindings() { Container.BindInterfacesAndSelfTo<MasterDataManager>().AsSingle(); Container.BindInterfacesAndSelfTo<PlayerDataRepository>().AsSingle(); Container.Bind<IResourceLoadProvider>().To<CsvLoader>().AsSingle(); } } 2019/08/26 Roppongi.unity #04 23
PlayerDataRepository だけ差し替えて動作は変えない public class MasterDataManager : IInitializable { public List<EnemyData>
Enemies { get; private set; } public List<ItemData> Items { get; private set; } [Inject] private IResourceLoadProvider _provider; [Inject] private PlayerDataRepository _playerDataRepository; public PlayerData FindById(int id) => _playerDataRepository.FindById(id); public void Initialize() { Enemies = _provider.LoadAll<EnemyData>("path/to/enemies"); Items = _provider.LoadAll<ItemData>("path/to/items"); } } 2019/08/26 Roppongi.unity #04 24
新規に書くときは直接Repository を渡してあげればよい public class FooBehaviour { [Inject] private PlayerDataRepository _repository;
public void Foo(){ var player = repository.FindById(1); } } 2019/08/26 Roppongi.unity #04 25
Test もZenject 対応 public class MasterDataManagerTest : ZenjectUnitTestFixture { public
class MockLoader : IResourceLoadProvider { public List<T> LoadAll<T>(string path) => new List<T>(/* なんかいい感じ生成 */); } private MasterDataManager _manager; [Test] public void FindById_Success() { Container.Bind<MasterDataManager>().AsSingle(); Container.BindInterfacesTo<MockLoader>().AsSingle(); Container.Bind<PlayerDataRepository>().AsSingle(); _manager = Container.Resolve<MasterDataManager>(); _manager.Initialize(); var playerData = _manager.FindById(1); Assert.AreEqual("Foo", playerData.name); } } 2019/08/26 Roppongi.unity #04 26
さよならManager 2019/08/26 Roppongi.unity #04 27
まとめ Zenject 使えばインスタンスを集約するだけのクラスは不要になる Manager と呼ばれてるものの役割を見直してみよう もしかしたらインスタンスを集めてるだけのクラスかもしれない 漸進的に変更できる方法を選ぼう 旧コードと新コードが一時は同居できるようにする テストを書くと安心感ある テストを書くと安心感ある
テストを書くと安心感ある 2019/08/26 Roppongi.unity #04 28
宣伝 Zenject 本書いてます 技術書典7 こ20D 前後編になりました 〆切と印刷費に負けた ごめん 今回は前編を出します DI
の話~Scene 跨ぎのBind くらいまで 2019/08/26 Roppongi.unity #04 29