Mevcut durum
Mevcut kurulum, Arayüz Ayırma İlkesini (SOLID'deki I) ihlal ediyor.
Referans
Wikipedia'ya göre arayüz ayırma ilkesi (İSS), hiçbir müşterinin kullanmadığı yöntemlere bağımlı olmaya zorlanmaması gerektiğini belirtir . Arayüz ayrım ilkesi, 1990'ların ortalarında Robert Martin tarafından formüle edildi.
Başka bir deyişle, eğer arayüzünüz buysa:
public interface IUserBackend
{
User getUser(int uid);
User createUser(int uid);
void deleteUser(int uid);
void setPassword(int uid, string password);
}
Daha sonra bu arabirimi uygulayan her sınıf, listelenen her arabirim yöntemini kullanmalıdır . İstisna yok.
Genelleştirilmiş bir yöntem olup olmadığını düşünün:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
backendService.deleteUser(user.Uid);
}
Eğer gerçekten sadece bazı uygulayıcı sınıfların bir kullanıcıyı silebilmesi için bunu yapacak olsaydınız, bu yöntem zaman zaman yüzünüze patlar (ya da hiçbir şey yapmaz). Bu iyi bir tasarım değil.
Önerilen çözümünüz
IUserInterface, bit eylem VE bit eylem VE bit istenen VEed eylemleri sonucu olan bir tamsayı döndüren bir APPLICActions yöntemi olduğu bir çözüm gördüm.
Temel olarak yapmak istediğiniz şey:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
if(backendService.canDeleteUser())
backendService.deleteUser(user.Uid);
}
Belirli bir sınıfın bir kullanıcıyı silip edemediğini tam olarak nasıl belirlediğimizi görmezden geliyorum . İster boolean, ister biraz bayrak olsun, ... önemli değil. Her şey bir ikili cevaba kadar kaynar: bir kullanıcıyı silebilir mi, evet mi hayır mı?
Bu sorunu çözecektir, değil mi? Teknik olarak öyle. Ama şimdi, Liskov İkame İlkesini ( SOLID'deki L) ihlal ediyorsunuz .
Oldukça karmaşık Wikipedia açıklamasından sonra StackOverflow'da iyi bir örnek buldum . "Kötü" örneğe dikkat edin:
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
Sanırım burada benzerliği görüyorsunuz. Bu, soyutlanmış bir nesneyi ( IDuck
, IUserBackend
) işlemesi gereken bir yöntemdir , ancak güvenliği ihlal edilmiş bir sınıf tasarımı nedeniyle, ilk olarak belirli uygulamaları işlemek zorunda kalır ( bunun, kullanıcıları silemeyen ElectricDuck
bir IUserBackend
sınıf olmadığından emin olun ).
Bu soyutlanmış bir yaklaşım geliştirme amacını bozar.
Not: Buradaki örneği düzeltmek sizin durumunuzdan daha kolaydır. Örneğin, sahip olmak yeterli ElectricDuck
üzerindeki dönüş kendisi içerideSwim()
yöntemle. Her iki ördek de hala yüzebilir, bu yüzden fonksiyonel sonuç aynıdır.
Benzer bir şey yapmak isteyebilirsiniz. Yapma . Sadece olamaz taklit bir kullanıcıyı silmek ama gerçekte boş bir yöntem vücut var. Bu teknik açıdan işe yarıyor olsa da, uygulayıcı sınıfınızın bir şey yapması istendiğinde gerçekten bir şey yapıp yapmayacağını bilmek imkansız hale gelir. Bu, sürdürülemez kod için bir üreme alanıdır.
Önerilen çözümüm
Fakat bir uygulayıcı sınıfın bu yöntemlerden sadece bazılarını ele almasının mümkün (ve doğru) olduğunu söylediniz.
Örnek olarak, bu yöntemlerin olası her birleşimi için, onu uygulayacak bir sınıf olduğunu varsayalım. Tüm üslerimizi kapsar.
Buradaki çözüm arayüzü ayırmaktır .
public interface IGetUserService
{
User getUser(int uid);
}
public interface ICreateUserService
{
User createUser(int uid);
}
public interface IDeleteUserService
{
void deleteUser(int uid);
}
public interface ISetPasswordService
{
void setPassword(int uid, string password);
}
Bunun cevabımın başında geldiğini görebildiğinizi unutmayın. Arayüz Ayrışma İlkesi adı zaten bu prensip yapmak için tasarlanmıştır ortaya koymaktadır arayüzleri ayırmak yeterli derecede.
Bu, arayüzleri istediğiniz gibi karıştırmanıza ve eşleştirmenize olanak tanır:
public class UserRetrievalService
: IGetUserService, ICreateUserService
{
//getUser and createUser methods implemented here
}
public class UserDeleteService
: IDeleteUserService
{
//deleteUser method implemented here
}
public class DoesEverythingService
: IGetUserService, ICreateUserService, IDeleteUserService, ISetPasswordService
{
//All methods implemented here
}
Her sınıf, arayüzlerinin sözleşmesini bozmadan hangisini yapmak istediklerine karar verebilir.
Bu aynı zamanda belirli bir sınıfın bir kullanıcıyı silip edemediğini kontrol etmemiz gerekmediği anlamına gelir. IDeleteUserService
Arabirimi uygulayan her sınıf bir kullanıcıyı silebilir = Liskov İkame İlkesi ihlal edilmez .
public void HaveUserDeleted(IDeleteUserService backendService, User user)
{
backendService.deleteUser(user.Uid); //guaranteed to work
}
Herhangi biri uygulamayan bir nesneyi iletmeye çalışırsa IDeleteUserService
, program derlemeyi reddeder. Bu yüzden tip güvenliğine sahip olmayı seviyoruz.
HaveUserDeleted(new DoesEverythingService()); // No problem.
HaveUserDeleted(new UserDeleteService()); // No problem.
HaveUserDeleted(new UserRetrievalService()); // COMPILE ERROR
dipnot
Örneği aşırı derecede ele aldım, arayüzü mümkün olan en küçük parçalara ayırarak. Bununla birlikte, durumunuz farklıysa, daha büyük parçalarla kurtulabilirsiniz.
Örneğin, bir kullanıcı oluşturabilen her hizmet her zaman bir kullanıcıyı silme yeteneğine sahipse (veya tersi), bu yöntemleri tek bir arabirimin parçası olarak saklayabilirsiniz:
public interface IManageUserService
{
User createUser(int uid);
void deleteUser(int uid);
}
Daha küçük parçalara ayırmak yerine bunu yapmanın teknik bir yararı yoktur; ancak daha az kaynatma gerektirdiğinden gelişimi biraz daha kolaylaştıracaktır.
IUserBackend
İçermemelidirdeleteUser
hiç yöntemini. Bu bir parçası olmalıIUserDeleteBackend
(ya da ona ne demek istiyorsanız). Kullanıcıları silmesi gerekenIUserDeleteBackend
kodun argümanları, işlevselliğin kullanması gerekmeyen kodlarIUserBackend
ve uygulanmayan yöntemlerle herhangi bir sıkıntı yaşamayacaklardır.