teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

デレゲートパターンの説明を追加

2020/02/26 23:50

投稿

quadii.shii
quadii.shii

スコア257

answer CHANGED
@@ -1,3 +1,4 @@
1
+ # デレゲートパターンを使う場合
1
2
  ```Kotlin
2
3
  class MainActivity : AppCompatActivity() {
3
4
 
@@ -120,4 +121,135 @@
120
121
  * Addが発生したとき、デリゲートを実行する。
121
122
  `onAdded?.invoke()`
122
123
 
123
- という感じです。
124
+ という感じです。
125
+
126
+ ## デレゲートとは
127
+ デレゲート(delegate)とは委譲(実行をお願いする)という意味があり、このケースだと「MainActivityが自分の持つrefreshという関数を実行してくれるようAddDialogにお願いする」という意味になります。
128
+ AddDialogの`onAdded`という変数に`MainActivity::refresh`という関数の参照を保持しており、実際の呼び出し`invoke`が行われるのはAddDialog内ですが、実行されるのはMainActivity側の関数なので、MainActivity内のリソースを用いて行われます。
129
+
130
+ # デレゲートを使わないパターン
131
+ ちょっと特殊ですがデレゲート使わないパターンも載せておきます。
132
+ デレゲートパターンでも実際にやっていることはこれと同じになります。
133
+
134
+ ```Kotlin
135
+ class MainActivity : AppCompatActivity() {
136
+
137
+ private val _helper = DatabaseHelper(this@MainActivity)
138
+
139
+ override fun onCreate(savedInstanceState: Bundle?) {
140
+ super.onCreate(savedInstanceState)
141
+ setContentView(R.layout.activity_main)
142
+
143
+ val fab: View = findViewById(R.id.fab)
144
+ fab.setOnClickListener(object: View.OnClickListener{
145
+ override fun onClick(v: View?) {
146
+ // AddDialogを初期化するときMainActivity本体を渡してしまう。
147
+ val dialog = AddDialog(this@MainActivity)
148
+ // 追加があるとリストが再び初期化される
149
+ // デレゲートパターンは使わない
150
+ // dialog.onAdded = this@MainActivity::refresh
151
+ dialog.show(supportFragmentManager, "AddDialog")
152
+ }
153
+ })
154
+
155
+ val lv = findViewById<ListView>(R.id.lv)
156
+
157
+ // リスト初期化
158
+ refresh()
159
+ }
160
+
161
+   // 繰り返し使うので関数として切り出した。
162
+ // AddDialog内で呼び出したいのでprivateを外してクラス外側からアクセスできるようにする。
163
+ fun refresh() {
164
+ var itemsList : MutableList<String> = mutableListOf()
165
+ val db = _helper.writableDatabase
166
+ val sql = "SELECT * FROM items"
167
+ var cursor = db.rawQuery(sql, null)
168
+
169
+ val idxItems_name = cursor.getColumnIndex("items_name")
170
+
171
+ while(cursor.moveToNext()){
172
+ itemsList.add(cursor.getString(idxItems_name))
173
+ }
174
+
175
+ val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList)
176
+ lv.adapter = adapter
177
+ }
178
+
179
+ override fun onDestroy() {
180
+ _helper.close()
181
+ super.onDestroy()
182
+ }
183
+ }
184
+ ```
185
+
186
+ ```Kotlin
187
+ // AddDialogのプロパティとしてMainActivityを持たせてしまう。
188
+ class AddDialog(val mainActivity: MainActivity): DialogFragment(){
189
+
190
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
191
+
192
+ // デレゲートを使わないパターン
193
+ // var onAdded: (() -> Unit)? = null
194
+
195
+ val editText = EditText(activity)
196
+ val _helper = DatabaseHelper(activity as Context)
197
+
198
+ val builder = AlertDialog.Builder(activity)
199
+ builder.setTitle(R.string.add_dialog_title)
200
+ builder.setView(editText)
201
+
202
+ builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{
203
+ override fun onClick(dialog: DialogInterface?, which: Int) {
204
+ when(which){
205
+ DialogInterface.BUTTON_POSITIVE->{
206
+ val addItem = editText.text.toString()
207
+ if(addItem.length != 0) {
208
+
209
+ val db = _helper.writableDatabase
210
+
211
+ val sqlCheck = "SELECT * FROM items WHERE items_name = ?"
212
+ val parms = arrayOf(addItem)
213
+ val cursor = db.rawQuery(sqlCheck, parms)
214
+
215
+ //存在チェック
216
+ if(cursor.moveToNext() == true){
217
+ Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show()
218
+ }
219
+ else{
220
+ val sqlInsert = "INSERT INTO items (items_name) VALUES (?)"
221
+ var stmt = db.compileStatement(sqlInsert)
222
+ stmt.bindString(1, addItem)
223
+ stmt.executeInsert()
224
+
225
+ Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show()
226
+
227
+ // ここで直接mainActivityに所属するrefreshを実行する。
228
+ mainActivity.refresh()
229
+ }
230
+ }
231
+ else{
232
+ Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show()
233
+ }
234
+
235
+ }
236
+ }
237
+ }
238
+ })
239
+
240
+ builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{
241
+ override fun onClick(dialog: DialogInterface?, which: Int) {
242
+ Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show()
243
+ }
244
+ })
245
+
246
+ val dialog = builder.create()
247
+ return dialog
248
+ }
249
+ }
250
+ ```
251
+
252
+ かなり乱暴なやり方ですが、これでも動きます。
253
+ ## このやり方の悪い点
254
+ AddDialogを他のアプリで使いまわしたいと思ったとき、このMainActivity以外とは使えないことになってしまいます。
255
+ そのために古来よりインターフェースという素晴らしいパターンがあるのですが(OnClickListenerなどがそれ。論じると長くなるのでやめておきます)、最近の言語では単一の処理だけを委譲(お願い)したい場合は、圧倒的にデレゲートパターンを使います。

1

分かりづらそうなところを修正しました。

2020/02/26 23:50

投稿

quadii.shii
quadii.shii

スコア257

answer CHANGED
@@ -114,7 +114,10 @@
114
114
  ポイントしては、
115
115
 
116
116
  * AddDialogにデリゲート(コールバック)を追加。
117
+ `var onAdded: (() -> Unit)? = null`
117
118
  * 追加したデリゲートと同じ型の関数をMainActivity側で代入する。
119
+ `dialog.onAdded = this@MainActivity::refresh`
118
120
  * Addが発生したとき、デリゲートを実行する。
121
+ `onAdded?.invoke()`
119
122
 
120
123
  という感じです。