Android Projelerimizde S.O.L.I.D Prensiplerinin Uygulanması

Mert Melih Aytemur
Appcent
Published in
5 min readMar 18, 2024

Android projelerimizin mimarisini geliştirirken, SOLID prensiplerini uygulamak, sürdürülebilir ve ölçeklenebilir bir yapı oluşturmanın anahtarıdır.

Bu yazıda, bu beş temel prensibi nasıl entegre edebileceğimizi ve Android uygulama geliştirmedeki başarımıza nasıl katkıda bulunacaklarını detaylı bir şekilde ele alacağız.

Öncelikle S.O.L.I.D Nedir?

SOLID, nesne yönelimli programlamada kullanılan beş temel prensibi ifade eder ve daha temiz, düzenli ve esnek yazılım geliştirme sürecini destekler. 2000'lerin başında Robert C. Martin tarafından popüler hale getirilen bu prensipler, yazılımın bakımını ve güncellenmesini kolaylaştırır. SOLID, her bir harfi aşağıdaki prensipleri temsil eder:

1)Single Responsibility Principle (SRP): Bir sınıfın tek bir sorumluluğu olmalıdır. Birden fazla görevi üstlenen bir sınıf ayrılmalı, böylece kod daha anlaşılır ve bakımı kolaylaşır. Örneğin, bir restoranda her çalışanın ayrı bir görevi vardır; aşçı pişirir, garson servis yapar, bulaşıkçı yıkar. Yazılımda da her sınıfın belirli bir işlevi olmalıdır.

2)Open/Closed Principle (OCP): Yazılım entiteleri genişlemeye açık fakat değişikliğe kapalı olmalıdır. Yeni işlevler eklenirken mevcut kod değiştirilmemelidir. Bu, yazılımın gelişimini kolaylaştırır. Bir lego seti gibi, mevcut yapıya zarar vermeden yeni parçalar eklenebilir.

3)Liskov Substitution Principle (LSP): Alt sınıflar, üst sınıflarının yerine geçebilmelidir. Alt sınıfların, üst sınıfın işlevlerini değiştirmeden genişletebilmesi gerekir. Örneğin, oyuncaklar farklı şekillerde olabilir, ancak hepsi aynı kutuya sığmalıdır.

4)Interface Segregation Principle (ISP): Sınıflar, kullanmadıkları yöntemleri içeren arayüzleri uygulamamalıdır. Daha küçük, özelleşmiş arayüzler kullanılmalıdır, böylece gereksiz bağımlılıklardan kaçınılır. Öğrencilerin sadece ilgi alanlarına yönelik dersleri alması gibi, sınıflar da yalnızca gereken işlevleri içeren arayüzleri uygulamalıdır.

5)Dependency Inversion Principle (DIP): Yüksek seviyeli modüller, düşük seviyeli modüllere doğrudan bağımlı olmamalıdır; her iki modül de arayüzlere veya soyutlamalara bağımlı olmalıdır. Bu, yazılımın esnekliğini ve genişletilebilirliğini artırır. Elektrik prizleri ve cihazlar gibi, farklı cihazlar aynı priz tipine bağlanabilir.

Hadi şimdi sizlerle bu prensiplerin Android projelerimizdeki olabilecek karşılıklarına bir bakalım

Single Responsibility Principle:

Yanlış Kullanım:

Bu örnekte Logger sınıfı, hem konsola loglama işlemini hem de dosyaya loglama işlemini yapıyor. Bu, SRP'ye aykırıdır çünkü bir sınıfın tek bir sorumluluğu olmalıdır.

class Logger {
fun log(message: String) {
// Konsola loglama
println(message)

// Dosyaya loglama
val fileWriter = FileWriter("log.txt", true)
fileWriter.write(message)
fileWriter.close()
}
}

Doğru Kullanım:

Burada, ConsoleLogger ve FileLogger sınıfları birer sorumluluğu üstlenir: biri konsola loglama, diğeri ise dosyaya loglama işlemlerini yapar. Bu şekilde, SRP'ye uygun bir yapı elde edilir.

class ConsoleLogger {
fun log(message: String) {
// Yalnızca konsola loglama
println(message)
}
}

class FileLogger {
fun log(message: String) {
// Yalnızca dosyaya loglama
val fileWriter = FileWriter("log.txt", true)
fileWriter.write(message)
fileWriter.close()
}
}

Open Closed Principle:

Yanlış Kullanım:

Bu örnekte, MusicPlayer sınıfı sadece MP3 dosyalarını çalabilir durumdadır. Yeni bir ses formatı eklemek için sınıfı değiştirmek gerekecektir, bu da OCP'ye aykırıdır.

class MusicPlayer {
fun playMP3(file: File) {
// MP3 dosyasını çal
}
}

Doğru Kullanım:

Burada, AudioPlayer arayüzü tüm ses dosyalarını çalmak için bir şablon sağlar ve yeni bir ses formatı eklemek, mevcut kodu değiştirmeden mümkündür. Bu, OCP'nin sağladığı esneklik ilkesini takip eder.

interface AudioPlayer {
fun play(file: File)
}

class MP3Player : AudioPlayer {
override fun play(file: File) {
// MP3 dosyasını çal
}
}

class FLACPlayer : AudioPlayer {
override fun play(file: File) {
// FLAC dosyasını çal
}
}

class WAVPlayer : AudioPlayer {
override fun play(file: File) {
// WAV dosyasını çal
}
}

Liskov Substitution Principle

class MainRepository(
private val fileLogger : FileLogger
){
suspend fun login(){
try{
//Login operations that can cause error
}catch(e : Exception){
fileLogger.logFile(e.message.toString())
}
}
}

Yanlış Kullanım:

Burada, CustomFileLogger sınıfı, FileLogger sınıfından türemesine rağmen logFile metodunu override etmemiştir. Bu durumda, LSP'ye aykırı olabilir çünkü FileLogger ile CustomFileLogger arasında beklenmeyen davranış farklılıkları olabilir.

open class FileLogger{
open fun logFile(errorMessage : String){
val file : File("errors.txt")
file.appendText(
text = errorMessage
)
}
}

class CustomFileLogger : FileLogger() {
fun customErrorLog(errorMessage : String){
val file : File("custom_errors.txt")
file.appendText(
text = errorMessage
)
}
}

Doğru Kullanım:

Burada, CustomFileLogger sınıfı logFile metodunu override ederek, temel sınıfın beklenen davranışını sağlar ve LSP'yi uygular.

open class FileLogger{
open fun logFile(errorMessage : String){
val file : File("errors.txt")
file.appendText(
text = errorMessage
)
}
}

class CustomFileLogger : FileLogger() {
override fun logFile(errorMessage : String){
val file : File("custom_errors.txt")
file.appendText(
text = errorMessage
)
}
}

Interface Segregation Principle

Hatalı Kullanım:

Bu örnekte, FileLogger arayüzü hem logFile hem de printLog metodlarını içerir. Ancak, CustomFileLogger sınıfı printLog metodunu kullanmaz, bu da ISP'ye aykırıdır çünkü kullanılmayan metodları içeren arayüzler genellikle kötü bir tasarımı işaret eder.

interface FileLogger{

fun logFile(errorMessage : String)
fun printLog(errorMessage : String)


}

class CustomFileLogger : FileLogger {
override fun printLog(errorMessage : String) {
// Not using but bad usage
}
override fun logFile(errorMessage : String){
val file : File("custom_errors.txt")
file.appendText(
text = errorMessage
)
}
}

Doğru Kullanım:

Burada, FileLogger arayüzü varsayılan bir gövde sağlayarak printLog metodunu içerir. Böylece, CustomFileLogger sınıfı printLog metodunu override etmek zorunda kalmadan, isteğe bağlı olarak kullanabilir ve ISP'yi uygular.

interface FileLogger{

fun logFile(errorMessage : String)
fun printLog(errorMessage : String) {
// default body prevents force override
}


}

class CustomFileLogger : FileLogger {

//No need to override anymore

override fun logFile(errorMessage : String){
val file : File("custom_errors.txt")
file.appendText(
text = errorMessage
)
}
}

Dependency Inversion:

Yanlış Kullanım:

Bu örnekte, PaymentProcessor sınıfı doğrudan PayPalProvider sınıfına bağımlıdır. Bu, DIP'ye aykırıdır çünkü yüksek seviyeli bir sınıf, düşük seviyeli bir sınıfa doğrudan bağımlı olmamalıdır.

class PaymentProcessor {
private val paymentProvider = PayPalProvider()

fun processPayment(amount: Double) {
paymentProvider.pay(amount)
}
}

class PayPalProvider {
fun pay(amount: Double) {
println("Payment processed via PayPal: $amount")
// PayPal'a gerçek ödeme isteği gönderilir
}
}

Doğru kullanım:

Burada, PaymentProcessor sınıfı PaymentProvider arayüzüne bağımlıdır ve bu sayede farklı ödeme sağlayıcıları eklemek kolaylaşır, bu da DIP'yi uygular.

// PaymentProvider arayüzü
interface PaymentProvider {
fun pay(amount: Double)
}

// PayPalProvider sınıfı, PaymentProvider arayüzünü implement eder
class PayPalProvider : PaymentProvider {
override fun pay(amount: Double) {
println("Payment processed via PayPal: $amount")
// PayPal'a gerçek ödeme isteği gönderilir
}
}

// StripeProvider sınıfı, PaymentProvider arayüzünü implement eder
class StripeProvider : PaymentProvider {
override fun pay(amount: Double) {
println("Payment processed via Stripe: $amount")
// Stripe'a gerçek ödeme isteği gönderilir
}
}

// PaymentProcessor sınıfı, PaymentProvider arayüzüne bağımlıdır
class PaymentProcessor(private val paymentProvider: PaymentProvider) {
fun processPayment(amount: Double) {
paymentProvider.pay(amount)
}
}

Sonuç

Android projelerimizin mimarisini geliştirirken, SOLID prensiplerini uygulamak, sürdürülebilir ve ölçeklenebilir bir yapı oluşturmanın anahtarıdır. Bu prensipler, her bir harfi temsil ettiği SRP, OCP, LSP, ISP ve DIP ilkeleriyle yazılım geliştirme sürecinde daha temiz ve esnek bir yaklaşım sunar. Bu sayede, kodun daha anlaşılır, bakımı daha kolay ve yeni işlevlerin eklenmesi daha sorunsuz hale gelir. SOLID prensiplerini doğru şekilde uygulamak, Android uygulama geliştirmenin başarısını artırır ve kod tabanını daha sağlam hale getirir.

İyi Çalışmalar Dilerim.

--

--