2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Android 疎結合について

Last updated at Posted at 2024-09-22

概要

疎結合、密結合についてそれぞれ内容を整理する。

この記事を見て理解できること

  • 密結合とは何か
  • 疎結合とは何か
  • 密結合が良くない理由

密結合とは

密結合は、異なるコンポーネント(クラスやモジュール)が強く依存し合っている状態を指します。

密結合になっている例

例えば、Recycler Viewを使用してそれぞれのitemでクリックイベントを用意したいケースがあるとします。
また、その場合の密結合になっているコード例は以下です。

class MyAdapter(
    private val items: List<String>, 
    private val fragment: MyFragment // Fragmentに依存
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.textView.text = items[position]

        // Fragmentのメソッドを直接呼び出してクリックイベントを処理
        holder.binding.root.setOnClickListener {
            (fragment as MyFragment).onItemClicked(items[position])
        }
    }

    override fun getItemCount(): Int = items.size
}
class MyFragment : Fragment() {

    private lateinit var adapter: MyAdapter
    private var _binding: FragmentMyBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMyBinding.inflate(inflater, container, false)

        val items = listOf("Item 1", "Item 2", "Item 3")

        // Fragment自体をAdapterに渡してインスタンス化(密結合)
        adapter = MyAdapter(items, this)
        binding.recyclerView.adapter = adapter

        return binding.root
    }

    // Adapterから直接呼ばれるメソッド
    fun onItemClicked(item: String) {
        println("Tight Coupling")
    }
}

上記からわかる通り、AdapterがFragmentに直接依存しており、Adapterの内部でFragmentのメソッドを直接呼び出してます。
これによる弊害については例えば以下が挙げられます。

  • FragmentのonItemClicked()を変更した時、Adapter側も修正が必要になる
  • 違うFragmentでMyAdapterを使用する場合、都度、setOnClickListnerを修正しなければならない

このことから、密結合は再利用性の低下につながります。

疎結合とは

疎結合とは「コンポーネント同士の結合が緩く、独立している」ことを指します。実装をイメージすると「実態を知らなくても利用できる」状態のことを指します。
 →ここでのコンポーネント同士の結合がゆるいとは実装に依存せず、インターフェースに依存する結合関係のことを指します。

疎結合できている例

上記の例を疎結合する場合、以下のコードになります。

interface OnItemClickListener {
    fun onItemClicked(item: String)
}
class MyAdapter(
    private val items: List<String>, 
    private val listener: OnItemClickListener // インターフェースを通じてイベントを通知
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.textView.text = items[position]

        // インターフェースを使用してクリックイベントを外部に通知
        holder.binding.root.setOnClickListener {
            listener.onItemClicked(items[position])
        }
    }

    override fun getItemCount(): Int = items.size
}
class MyFragment : Fragment(), OnItemClickListener {

    private lateinit var adapter: MyAdapter
    private var _binding: FragmentMyBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMyBinding.inflate(inflater, container, false)

        val items = listOf("Item A", "Item B", "Item C")

        // Fragmentがインターフェースを実装し、Adapterに渡す(疎結合)
        adapter = MyAdapter(items, this)
        binding.recyclerView.adapter = adapter

        return binding.root
    }

    // インターフェースのメソッドを実装
    override fun onItemClicked(item: String) {
        println("Tight Coupling")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

上記では、AdapterとFragmentがインターフェースを介してやり取りしているため、疎結合になっており、双方を独立して変更可能になってます。

密結合が良くない理由

上記の説明でもありましたが、密結合によるデメリットをまとめると以下が挙げられます。

密結合のデメリット

  • メンテナンスが困難になる
    • モジュール同士が強く依存しているため、1つのコンポーネントに変更を加えると他の部分にも影響が及びやすい
  • 再利用性の低下
  • 柔軟性と拡張性の欠如
    • システムやアプリケーションの一部を変更、拡張したり、新機能を追加する場合、密結合された部分は他の部分に大きく依存しているため、簡単に拡張することが難しい(例に記載した通り)
  • 単体テストが難しくなる
    • コンポーネントが互いに依存し合うと、単独でのテストが難しくなり、テスト範囲が広がるため、テストの複雑性が増す

まとめ

今回DIの説明等はなかったですが、密結合を避けるために、設計時には「疎結合」(loose coupling)を目指し、以下のアプローチを採用することが重要です。

  • 依存性注入(Dependency Injection)
  • インターフェースや抽象クラスの使用(今回の内容)
  • モジュール化やコンポーネントベースの設計
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?