Android Projelerimizde S.O.L.I.D Prensiplerinin Uygulanması
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.