اصول S.O.L.I.D در کاتلین
اصول S.O.L.I.D در برنامهنویسی مجموعهای از اصول طراحی است که برای ایجاد کد منبعی قابل خواندن، قابل توسعه، و قابل حفظ استفاده میشود. این اصول توسط رابرت سی. مارتین (Robert C. Martin) در دهه ۲۰۰۰ معرفی شدند و هر یک از حروف این کلمه یک اصل را نمایان میکنند:
Single Responsibility Principle (SRP): هر ماژول یا کلاس باید فقط یک مسئولیت یا کار مشخص را داشته باشد و تغییرات باید فقط به دلیل تغییر یک مسئولیت صورت گیرد.
Open/Closed Principle (OCP): برنامه باید باز برای توسعه باشد اما بسته برای تغییر. این به معنای آن است که میتوانیم ویژگیهای جدید را به برنامه اضافه کنیم بدون اینکه کد موجود را تغییر دهیم.
Liskov Substitution Principle (LSP): زیرکلاسها باید به جای کلاسهای پایه خود قابل جایگزینی باشند بدون اینکه خصوصیات برنامه را تغییر دهند.
Interface Segregation Principle (ISP): کلاینتها فقط باید به توابعی که نیاز دارند وابسته باشند و نباید به توابعی که استفاده نمیکنند وابسته باشند.
Dependency Inversion Principle (DIP): کلاسها باید به یک واسط سطح بالاتر از خودشان وابسته باشند تا به جای یکدیگر. این اصل انعطافپذیری و قابلیت تست را افزایش میدهد و از وابستگی به جزئیات پیادهسازی مستقیم جلوگیری میکند.
1- اصل single responsibility (مسئولیت واحد)
اصل single responsibility بیان می کند که هر class باید تنها یک مسئولیت داشته باشد.
data class User(
var id:Long,
var name:String,
var password:String
){
Fun signIn(){
//this method will do signing in operation
}
Fun signOut(){
// this method will do signing out operation
}
}
}
دیتا مدل
data class User(
var id:Long,
var name:String,
var password:String
)
Class AuthentivationService(){
Fun signIn(){
//this method will do signing in operation
}
Fun signOut(){
// this method will do signing out operation
}
}
اگر
حالا فرض کنیم که ما باید در متدهای sign-in و sign-out ، فرایند authentication تعدادی تغییر ایجاد کنیم (شکل 1) ، در این صورت کلاس user ما تحت تأثیر قرار می گیرد.
آنگاه
برای حل مسئله باید کلاس جدیدی (شکل 2) ایجاد کنیم تا فرآیند authentication را مدیریت کرده و متدهای sign-in و sign-out را در آن کلاس move کنیم.
2- اصل open-closed
open: این بدان معناست که ما می توانیم feature های جدیدی را به کلاس های خود اضافه کنیم.
closed: این بدان معناست که feature های اصلی کلاس نباید تغییر کند.
class Rectangle{
private var length
private var height
//getters / setters …
}
class Circle{
private var radius
//getters / setters …
}
class AreaFactory{
public fun calculateArea(shapes:ArrayList<Object>):Double{
var area = 0
for (shape in shapes){
if(shape is Rectangle) {
var rect = shape as Rectangle
area += (rect.getLength()*rect.getHeight())
}
else if (shape is Circle){
var circle = shape as Circle
area += (circle.getRadius()*circle. getRadius ()*Math.PI)
}
else{
throw new RunTimeExeption(“shape not supported”)
}
}
return area
}
}
پیاده سازی مدل ها
Public interface Shape {
fun getArea()
}
class Rectangle : Shape {
private var length
private var height
//getters / setters …
override fun getArea():Double {
return (length*height)
}
class Circle: Shape {
private var radius
//getters / setters …
override fun getArea():Double{
return (radius * radius *Math.PI)
}
class AreaFactory{
fun calculateArea(shapes:ArrayList<String>):Double{
var area:Double = 0.toDouble()
for (shape in shapes){
area += shape.getArea()
}
return area
}
}
اگر
در شکل 1 می بینیم که یک دستور if else برای جدا کردن shape ها داریم ، و با افزایش shape ها ، آنها نیز به همان ترتیب به رشد خود ادامه می دهند ، زیرا کلاس برای modification (اصلاح) بسته نشده است ، یا برای extention (گسترش) نیز باز نیست.
آنگاه
ما با کمک interface این مشکل را برطرف می کنیم. در صورت نیاز به افزودن shape های بیشتر ، کلاس area factory به هیچ تغییری نیاز ندارد زیرا این کلاس از طریق اینترفیس shape ، برای توسعه باز است.
3- اصل جایگزینی liskov
کلاس فرزند (child) باید جایگزین کلاس پدر (parent) شود.
abstract class Vehicle {
protected var isEngineWorking = false
abstract fun startEngine()
abstract fun stopEngine()
abstract fun moveForward()
abstract fun moveBack()
}
class Car:Vehicle() {
override fun startEngine() {
println("Engine started")
isEngineWorking = true
}
override fun stopEngine() {
println("Engine stopped")
isFngineWorking = false
}
override fun moveForward() {
println("Moving forward")
}
override fun moveBack() {
println("Moving back!")
}
}
class Bicycle:Vehicle(){
override fun startEngine() {
TODO("Not yet implemented")
}
override fun stopEngine() {
TODO("Not yet implemented")
}
override fun moveForward() {
println("Moving forward")
}
override fun moveBack() {
println("Moving back!")
}
}
پیاده سازی دیتا مدل ها
interface Vehicle {
fun move forward()
fun moveBack
}
abstract class VehicleWithEngine:Vehicle {
private var isEngineWorking = false
open fun startEngine(){isEngineWorking = true}
open fun stopEngine(){isEngineWorking = false)
}
class Car:VehicleWithEngine(){
override fun startEngine() {
super.startEngine()
println("Engine started")
}
override fun stopEngine() {
super.stopEngine()
println("Engine stopped")
}
override fun moveForward() {
println("Moving forward")
}
override fun moveBack() {
println("Moving back!")
}
}
class Bicycle:Vehicle{
override fun moveForward() {
println("Moving forward")
}
override fun moveBack() {
println("Moving back!")
}
}
اگر
در شکل 1 هنگام ایجاد کلاس دوچرخه ، متدهای startEngine و stopEngine بی فایده خواهند بود زیرا دوچرخه موتور ندارد.
آنگاه
برای رفع این حالت ، ما می توانیم یک کلاس فرزند (child) جدید ایجاد کنیم (شکل 2) که از Vehicle ، extend می شود. این کلاس دارای موتور خواهد بود
4- اصل Interface segregation (تفکیک Interface)
این اصل بیان می کند که آن دسته از interface هایی که بسیار شلوغ و بزرگ(دارای متدهای اضافه) هستند ، باید به interface های کوچکتر تقسیم شود.
interface Animal {
fun eat()
fun sleep()
fun fly()
}
class Cat : Animal {
override fun eat() {
println("Cat is eating fish")
}
override fun sleep() {
println("Cat is sleeping on its owner's bed")
}
override fun fly() {
TODO ( "Not yet implemented") //cat can not fly!!
}
}
class Bird : Animal {
override fun eat() {
println("Bird is eating forage")
}
override fun sleep() {
println("Bird is sleeping in the nest")
}
override fun fly(){
println("Bird is flying in so high"
}
}
دیتا مدل
interface Animal {
fun eat()
fun sleep()
}
interface FlyingAnimal {
fun fly()
}
class Cat : Animal {
override fun eat() {
println("Cat is eating fish")
}
override fun sleep() {
println("Cat is sleeping on its owner's bed")
}
}
class Bird : Animal, FlyingAnimal {
override fun eat() {
println("Bird is eating forage")
}
override fun sleep() {
println("Bird is sleeping in the nest")
}
override fun fly() {
println("Bird is flying in so high")
}
اگر
همانطور که در شکل 1 مشاهده می کنید برخی از حیوانات مانند گربه ها نمی توانند پرواز کنند . در حالی که implement و اجرای متد fly در آن ضروری است.
آنگاه
برای رفع این مشکل ، یک interface جدید (شکل 2) برای حیوانات پرنده ایجاد می کنیم و متد fly را از اینترفیس animalحذف می کنیم
5- اصل Dependency Inversion (وارونگی وابستگی)
این اصل بیان می کند که ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند.
class AndroidDeveloper {
fun developerMobileApp(){
println("developing Android Application by using kotlin")
}
}
class IosDeveloper {
fun developerMobileApp(){
println("developing iOS Application by using swift")
}
}
fun main(){
val androidDeveloper = AndroidDeveloper()
val iosDeveloper = IosDeveloper()
androidDeveloper.developerMobileApp()
iosDeveloper.developerMobileApp()
}
دیتا مدل ها
interface MobileDeveloper{
fun developerMobileApp()
}
class AndroidDeveloper (var mobileService: MobileServices): MobileDeveloper { override fun developerMobileApp(){
println("developing Android Application by using kotlin." +
"application will work with ${mobileService.serviceName}")
}
enum class MobileServices (var serviceName: String) {
HMS( serviceName: "Huawei Mobile Service"),
GMSC serviceName: "Google Mobile Service"),
BOTH( ServiceName: "Huawei Mobile Service and Google Mobile Service")
}
}
class IosDeveloper : MobileDeveloper {
override fun developerMobileApp({
println("developing iOS Application by using swift")
}
}
fun main{
val developers = arrayListof (AndroidDeveloper (AndroidDeveloper. MobileServices.HMS) , IosDeveloper())
developers.forEach { developer ->
developer.developerMobileApp()
}
}
اگر
بیایید فرض کنیم که ما باید یک اپلیکیشن را برای Android و ios در شکل 1 توسعه دهیم.
آنگاه
برای رفع مشکل ، می توانیم یک interface (شکل 2) ایجاد کنیم و کلاس های AndroidDeveloper و IosDeveloper این interface را implement (پیاده سازی) خواهند کرد.
دیدگاهتان را بنویسید