Gerçek dünyanın sorunlarını çözmek için bir yazılım uygulaması oluşturuyoruz. Genellikle yazılımı modüllere ayırırız ve modüller ayrıca sınıflara ayrılır. Single Responsibility Principle (SRP) yani Tek Sorumluluk İlkesi, her bir yazılım modülünün değişmesi için bir ve yalnızca bir nedeni olması gerektiğini belirtir. Diğer bir deyişle, aynı nedenlerle değişen şeyleri bir araya toplamalı. Farklı nedenlerle değişen şeyleri ayırmak üzerinedir. 

Değişimin Nedenine Nitelik Kazandırmak

Her sınıf veya modülün önceden tanımlanmış kendi sorumluluğu vardır. Gerçek dünyadaki bir kişiye yanıt verir, büyük olasılıkla değişiklik talebinden o kişi sorumlu olacaktır. Genel olarak, kodumuzu, değişiklikleri yalnızca bir kişi talep edecek şekilde yapılandırmalıyız.

Değişikliğin nedeni, yazılımın orijinal sorumluluğundaki bir değişiklikten başka bir şey değildir. Bir hata veya yeniden düzenleme, yazılımın sorumluluğu olmayıp geliştiricinin sorumluluğunda olduğu için bir değişiklik nedeni olarak değerlendirilemez.

Tek sorumluluk, bir sınıf içinde tek bir yönteme sahip olmak veya her küçük işlevsellik için ayrı bir sınıf oluşturmak anlamına gelmez,  tek bir nedeni olsa bile bunlar birbiriyle ilişkilidir.

Buraya kadar teorik bilgilerdi peki biraz örneklendirmek istersek; 

Tipik bir şartlar ve koşullar ekranı örneğini ele alalım. Kullanıcıya şartlar ve koşullar sunulacak, kabul etmek ve reddetmek için iki düğmesi var. Kullanıcının seçimini yerel depolama alanına kaydetmeliyiz.

SOLID kavramlarına hakim değilseniz ve Single Responsibility Principle mantığını uygulamadığınızda genellikle hardcode dediğimiz kodlamalarda aşağıdaki gibi sonuçlar çıkması gayet normaldir. 

class TermsAndConditionsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_terms_and_conditions)

        val tvTermsAndConditions = findViewById<TextView>(R.id.tvTermsAndConditions)
        tvTermsAndConditions.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas quis tellus quis erat rhoncus tempus sed sed ipsum. Aliquam ac ornare libero, a sodales ligula."

        val btnAccept = findViewById<Button>(R.id.btnAccept)
        btnAccept.setOnClickListener {
            val sharedPreference = getSharedPreferences("UserPreference", Context.MODE_PRIVATE)
            val editor = sharedPreference.edit()
            editor.putBoolean("terms_and_condition_accepted", true)
            editor.commit()
        }

        val btnDeny = findViewById<Button>(R.id.btnDeny)
        btnDeny.setOnClickListener {
            val sharedPreference = getSharedPreferences("UserPreference", Context.MODE_PRIVATE)
            val editor = sharedPreference.edit()
            editor.putBoolean("terms_and_condition_accepted", false)
            editor.commit()
        }
    }
}

Şimdi bu kod parçası için tek sorumluluk ilkesini uygulamaya çalışalım. Bu sınıftaki değişikliklerin üç nedeni olabilir, başka bir deyişle, değişiklik isteği bu sınıfı değiştirecek üç farklı kişi olabilir.

  • UI Tasarım Mühendisi - Piyasada yeni bir tür UI tasarım trendi var ve UI tasarım ekibi, bir değişiklik talep edebilmek için bunu mevcut bir uygulamaya dahil etmek istiyor.
  • Yazılım Mimarı - Kullanıcının tercihini kaydetmek için yerel depolama kullanıyoruz. Şimdi en son güvenlik önlemleri nedeniyle, bu bilgileri sunucumuzda saklamamız gerekiyor. Bu senaryoda, yazılım mimarı bir değişiklik talep edecektir.
  • Hukuk Danışmanı - Mevcut hüküm ve koşullarda bazı değişiklikler var. Yeni şartlar ve koşullar ekrana geldiğinde kullanıcıya sunulmalıdır, böylece bir değişiklik talebi olabilir.

Şimdi potansiyel değişim noktasında net olduğumuza göre, her biri için bir sınıf oluşturalım.

UI ile ilgili şeyler için etkinliği kullanmaya devam edeceğiz. UI tasarım mühendisinin yapacağı başka herhangi bir değişiklik yalnızca bu sınıfı etkileyecektir.

class TermsAndConditionsActivity : AppCompatActivity() {
    private lateinit var termsAndConditionsPresenter: TermsAndConditionsPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_terms_and_conditions)

        termsAndConditionsPresenter = TermsAndConditionsPresenter(this)

        val btnAccept = findViewById<Button>(R.id.btnAccept)
        btnAccept.setOnClickListener {
            termsAndConditionsPresenter.onAccept()
        }

        val btnDeny = findViewById<Button>(R.id.btnDeny)
        btnDeny.setOnClickListener {
            termsAndConditionsPresenter.onDeny()
        }
    }

    fun showTermsAndCondition(termsAndConditions: String) {
        val tvTermsAndConditions = findViewById<TextView>(R.id.tvTermsAndConditions)
        tvTermsAndConditions.text = termsAndConditions
    }

    override fun onDestroy() {
        termsAndConditionsPresenter.onDestroy()
        super.onDestroy()
    }
}

Yazılım mimarları, tercihi yerel veya uzak bir konuma kaydetmemizi isteyebilir. Kullanıcının tercihini sürdürmekle ilgili sınıf oluşturalım.

class TermsAndConditionsRepository(context: Context) {
    private val sharedPreference = context.getSharedPreferences("UserPreference", Context.MODE_PRIVATE)


    fun updateTermsAndConditionPreference(accepted: Boolean) {
        with(sharedPreference.edit()) {
            putBoolean(TERMS_AND_CONDITION_KEY, accepted)
            commit()
        }
    }

    companion object {
        private const val TERMS_AND_CONDITION_KEY = "terms_and_condition_preference"
    }
}

Daha iyi çözüm, bu durumla ilgilenmek için uygulanan bir depo modeline sahip olmak olacaktır. Basitlik adına, basit bir sınıf oluşturmayı seçtim. Burada arşiv modeli hakkında daha fazla ayrıntı bulabilirsiniz.

Hukuk danışmanı, şartlar ve koşullarda bazı değişiklikler önerir. Bu kod ne depoya ne de kullanıcı arayüzüne ait değildir, bir iş gereksinimidir. Bu tür gereksinimleri izole edecek ViewModel veya Presenter'ı kullanabiliriz.

class TermsAndConditionsPresenter(private var termsAndConditionsActivity: TermsAndConditionsActivity?) {
    private var termsAndConditionsRepository: TermsAndConditionsRepository? = null

    init {
        termsAndConditionsActivity?.let {
            termsAndConditionsRepository = TermsAndConditionsRepository(it)
            it.showTermsAndCondition("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas quis tellus quis erat rhoncus tempus sed sed ipsum. Aliquam ac ornare libero, a sodales ligula.")
        }
    }

    fun onAccept() {
        termsAndConditionsRepository?.updateTermsAndConditionPreference(true)
    }

    fun onDeny() {
        termsAndConditionsRepository?.updateTermsAndConditionPreference(false)
    }

    fun onDestroy() {
        termsAndConditionsActivity = null
        termsAndConditionsRepository = null
    }
}

Daha iyi çözüm, sunum yapan kişi için sözleşmeler içeren bir arabirim yazmak ve bunları ayırmak için görüntülemektir. Burada basitlik adına, sınıfı doğrudan kullanmayı seçtik.

Bu değişiklikleri yaparak, olası değişikliklerin etkilerini izole ettik. Değişiklikler daha spesifik olacak ve bazı ilgisiz kısımlardan işlevsellik kırılma riski oluşturmayacaktır. Bir diğer önemli fayda, tek bir sorumluluğu olan sınıfın açıklaması, anlaması ve uygulaması çok daha kolaydır. Bu, hata sayısını azaltır, geliştirme hızınızı artırır.

Unutmayın arkadaşlar Yazılımda kodlama yaparken ne kadar fazla kurallara uygun hareket edilirse netice o kadar başarılı ve temiz bir iş olacaktır.