SOLID Prensiplerine Bakış

Nesne Yönelimli Programlama'yı İyi Yapmanın Sırrı

Posted by Ali Özgür on March 2, 2018

SOLID kısaltması Robert C. Martin (Uncle Bob - Bob Amca olarak da bilinir) tarafından ortaya atılan bir dizi Nesne Yönelimli Programlama (OOP - Object Oriented Programming) prensiplerinden 5 tanesinin baş harflerinden oluşturulmuş ve ilk defa Michael Feathers tarafından ortaya atılmış bir kısaltmadır. Hem Bob Amca hem de Michael Feathers OOP yaklaşımının yaygınlaşmasında ve doğru bir şekilde kavranmasında önemli rolleri olan kişilerdir. Bu yazımda iyi bir yazılım geliştiricinin mutlaka bilmesi gerektiğini düşündüğüm SOLID prensiplerini en yalın hali ile ve kısa C# örnekleri ile size aktarmayı hedefliyorum.

SOLID Prinsiples

İstanbul Bilgi Üniversitesi Yazılım Geliştirme Ekibi olarak Cuma sabahları 10:00 - 12:00 arasında programlama dilleri, frameworkler (Türkçesini tam oturtamadım) ve genel olarak iyi yazılım geliştirme pratikleri üzerine az sunumlu bol kodlu oturumlar yapıyoruz.

BİLGİ Software Development Team

BİLGİ BT - Yazılım Geliştirme Ekibi (29 Aralık 2017)

Bu oturumlardan sonuncusunda SOLID prensiplerini ele aldık. SOLID prensipleri, anlaşılabilir, esnek ve yönetilebilir OOP kod yazmak için takip edilmesi önerilen pratikleri ifade eder. Bana sorarsanız SOLID sadece OOP kapsamında değil diğer programlama yaklaşım ve disiplinlerinde de oldukça kullanışlı bir dizi prensibi ifade ediyor. SOLID prensipleri, C# ve Java gibi OOP diller ile çalışırken ne kadar faydalı ise çok uç bir örnek olarak PL-SQL ve T-SQL gibi SQL dillerinden birisi ile çalışırken de faydalı bir düşünsel çerçeve sunabilir. Lafı çok uzatmadan SOLID kısaltmasını oluşturan prensipleri hızlıca açıklayalım.

  • Single Responsibility : Sınıflarımızın iyi tanımlanmış tek bir sorumluluğu olmalı.
  • Open/Closed : Sınıflarımız değişikliğe kapalı ancak yeni davranışların eklenmesine açık olmalı.
  • Liskov Substitution : Kodumuzda herhangi bir değişiklik yapmaya gerek kalmadan türetilmiş sınıfları (sub class) türedikleri ata sınıfın (base class) yerine kullanabilmeliyiz.
  • Interface Segregation : Genel kullanım amaçlı tek bir kontrat yerine daha özelleşmiş birden çok kontrat oluşturmayı tercih etmeliyiz.
  • Dependency Inversion : Katmanlı mimarilerde üst seviye modüller alt seviyedeki modüllere doğruda bağımlı olmamalıdır.

Gelin şimdi bu prensipleri adım adım oluşturacağımız basit bir örnek ile ele alalım.

Amacımız

Freformatter.com‘un sunduğu Json, HTML, JavaScript formatlama/güzelleştirme işlevine benzer işlevi sunan bir modül tasarlamak. Örneğimizde, formatlama işlemini yapmak yerine sınıf tasarımlarımızın SOLID prensiplerine uygun olarak nasıl tasarlayabileceğimize odaklanacağız.

Formatlama/güzelleştirme, herhangi bir formatlama kuralına uyulmadan rastgele oluşturulmuş ilk örnektekine benzer Json (Html, Xml, JavaScript, C#, PL-SQL de olabilir) metnin formatlanarak ikinci örnekteki hale getirilmesidir.

Formatlanmamış metin

{    "id":1,"ad"    :"Ali",     "soyad":  "Özgür"}

Formatlanmış metin

{
    "id": 1,
    "ad": "Ali",
    "soyad": "Özgür"
}

Kod örneklerini takip edebilmek için C# veya Java, C++ benzeri OOP dillerden birisini okuyabilmeniz yeterli olacaktır.

Single Responsibility (Tek Sorumluluk)

Sınıflarımızın iyi tanımlanmış tek bir sorumluluğu olmalı.

Sınıflarımızın sorumluluklarını tasarlarken en zorlandığımız aşama bu sorumlulukların neyi kapsayıp neleri kapsamayacağına karar vermektir. Aşağıdaki JsonFormatter sınıfını olabildiğince basit bir şekilde ve tek sorumluluğu verilen metni Format methodu ile formatlayacak şekilde oluşturuyoruz.

public class JsonFormatter
{
    public string Format(string input)
    {
        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

JsonFormatter sınıfının işlevi üzerinde düşündükçe şunu farkediyoruz; Json metinler belirli kurallara uygun olarak oluşturulması gereken metinlerdir. Bu nedele formatlama işleminin ön aşaması olarak verilen metnin geçerli bir Json metin olup olmadığının da denetlenmesi (validation) gerekir. JsonFormatter sınıfını IsInputValid metodunu da ekleyerek aşağıdaki gibi düzenliyoruz.

public class ValidationException:ApplicationException{}

public class JsonFormatter
{
    public string Format(string input)
    {
        // Kural denetimi talep et
        if (!IsInputValid(input))
            throw new ValidationException();
        
        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }

    private bool IsInputValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

İşler tam da bu noktada biraz sıkıntılı bir hal almaya başlıyor, çünkü IsInputValid metodunun eklenmesi ile birlikte JsonFormatter sınıfında Single Responsibility prensibinden uzaklaşmaya başlıyoruz. Sınfımıza Json formatlama sorumluluğuna ilave olarak bir de Json kural denetimi sorumluluğunu ekledik.

JsonFormatter sınıfını fazladan kural denetleme sorumluluğundan kurtarmak için yapmamız gereken şey aslında çok basit; yeni bir kural denetim sınıfı oluşturup bu sorumluluğu o sınıfa vermek. Aşağıdakine benzer bir JsonValidator sınıfı ile kural denetimlerini yapabiliriz.

public class JsonValidator
{
    public bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

JsonFormatter sınıfını kural denetimi sorumluluğundan kurtardığımıza göre aşağıdaki gibi yeniden düzenleyerek tekrar Single Responsibility prensibine uygun hale getirebiliriz.

public class JsonFormatter
{
    private JsonValidator _validator = new JsonValidator();

    public string Format(string input)
    {
        // Kural denetimini _validator nesnesine yaptır
        if (!_validator.IsValid(input))
            throw new ValidationException();

        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

Open/Closed (Açık/Kapalı)

Sınıflarımız değişikliğe kapalı ancak yeni davranışların eklenmesine açık olmalı.

Bu prensip daha çok alt seviyede işlevlere erişimi sağlayan birleştirici sınıflar için geçerlidir. Formatlama modülümüzün amaçlarından bir tanesi de birden fazla metin tipi (Json, Html, Xml vb) için formatlama işlevi sunmaktır. Bu amacı gerçekleştirmek için PrettyFormatter isimli birleştirici bir sınıf tanımlayalım. Bu sınıfın sorumluluğu seçilen metin tipi için formatlama işlevi sağlamak olsun.

public class PrettyFormatter
{
    // Format tipleri
    public enum FormatTypes
    {
        Json,
        Html
    }

    // Formatlama işlemini yapacak olan nesneler
    private JsonFormatter _jsonFormatter = new JsonFormatter();
    private HtmlFormatter _htmlFormatter = new HtmlFormatter();

    // Formatlama işlemini yapan metod
    public string Format(FormatTypes inputType, string input)
    {
        switch (inputType)
        {
            case FormatTypes.Json:
                return _jsonFormatter.Format(input);
            case FormatTypes.Html:
                return _htmlFormatter.Format(input);
            default:
                throw new Exception("Desteklenmeyen format tipi!");
        }
    }
}

Yukarıdaki hali ile PrettyFormatter sınıfı Json ve Html tipinden metinler için formatlama işlevi sunar. PrettyFormatter sınıfına Xml için de formatlama desteği eklemek istersek XmlFormatter ve XmlValidator sınıflarını tanımladıktan sonra PrettyFormatter sınıfında aşağıda yorum satırlarında numaralandırılmış 3 değişikliğin yapılması gerekir.

public class PrettyFormatter
{
    // Format tipleri
    public enum FormatTypes
    {
        Json,
        Html,
        Xml // (1) <-- !!!
    }

    // Formatlama işlemini yapacak olan nesneler
    private JsonFormatter _jsonFormatter = new JsonFormatter();
    private HtmlFormatter _htmlFormatter = new HtmlFormatter();
    private XmlFormatter _xmlFormatter = new XmlFormatter(); // (2) <-- !!!

    // Formatlama işlemini yapan metod
    public string Format(FormatTypes inputType, string input)
    {
        switch (inputType)
        {
            case FormatTypes.Json:
                return _jsonFormatter.Format(input);
            case FormatTypes.Html:
                return _htmlFormatter.Format(input);
            case FormatTypes.Xml: // (3) <-- !!!
                return _xmlFormatter.Format(input);
            default:
                throw new Exception("Desteklenmeyen format tipi!");
        }
    }
}

Bu düzenlemeler ile PrettyFormatter sınıfına Xml formatlama işlevini de ekledik ancak Open/Closed (Açık/Kapalı) prensibine aykırı olarak tam üç yerde değişiklik yapmak zorunda kaldık. Bu sorunu gidermek için takip edilebilecek yöntemlerden birisi formatlama işlemini genel hatları ile tanımlayan ata bir sınıf oluşturup (PrettyFormatProvider) asıl işlevleri de türetilmiş sınıfların (JsonFormatter vb) sorumluluğu olarak tanımlayabiliriz.


// Soyut kontrat sınıfı (Ata Sınıf)
public abstract class PrettyFormatProvider
{
    public abstract string Format(string input);
}

// Somut özelleşmiş sınıf (Türetilmiş Sınıf)
public class JsonFormatter : PrettyFormatProvider
{
    private JsonValidator _validator = new JsonValidator();

    public override string Format(string input)
    {

        if (!_validator.IsValid(input))
            throw new ValidationException();

        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

// Somut özelleşmiş sınıf (Türetilmiş Sınıf)
public class HtmlFormatter : PrettyFormatProvider
{
    private HtmlValidator _validator = new HtmlValidator();

    public override string Format(string input)
    {

        if (!_validator.IsValid(input))
            throw new ValidationException();

        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

Bu düzenlemelerden sonra PrettyFormatter sınıfının Format metodu aşağıdaki gibi yazılabilir. PrettyFormatter sınıfı ile PrettyFormatProvider kontratına uygun olarak tasarlanmış herhangi bir özelleşmiş formatlama sınıfı kullanılarak ve kod değişikliğine gerek kalmadan işlem yapabilir.

public class PrettyFormatter
{
    // Formatlama işlemini yapan metod
    public string Format(PrettyFormatProvider provider, string input)
    {
        return provider.Format(input);
    }
}

Liskov Substitution (Yerine Geçebilme)

Kodumuzda herhangi bir değişiklik yapmaya gerek kalmadan türetilmiş sınıfları (sub class) türedikleri ata sınıfın (base class) yerine kullanabilmeliyiz.

Yerine geçebilme prensibi OOP tasarımlarımızda ata sınıflar kullanılarak kontratlar tanımlanması ve bu kontratların taviz verilmeden takip edilmesi gerektiğini ifade eder. Yerine geçebilme prensibine uygun ve kontratlara dayalı sınıf tasarımları bir anlamda açık/kapalı prensibine uygunluğu da tetikler.

Gelin şimdi validator sınıflarımıza odaklanarak yerine geçebilme prensibini ele alalım. XML dokümanlarını basit ve genel XML söz dizimi kurallarına göre doğrulayabildiğimiz gibi verilen bir şemaya (XSD) göre de doğrulayabiliriz. Örnek XmlFormatter sınıfının XSD şemaları ile kural denetimi yapmasını sağlayabilmek için aşağıdaki gibi basit bir XmlSchemaValidator sınıfı tanımlayalım.

public class XmlSchemaValidator
{
    private string _xsdSchema;
    public XmlSchemaValidator(string xsd)
    {
        _xsdSchema = xsd;
    }

    public bool IsValid(string input)
    {
        // XSD şemaya göre kural denetimini yap
        return true;
    }
}

XmlFormatter sınıfını XmlSchemaValidator ile de çalışabilecek hale getirmek için aşağıdaki değişiklikleri yapmamız gerekiyor. Bunlar sırası ile;

  1. XmlSchemaValidator tipinden değişken tanımı
  2. XSD şemanın dışarıdan verilmesi ve buna göre doğru denetleme sınıfının oluşturulması için yapıcı metod (constructor) tanımı
  3. Format metodunun yapıcı metod ile verilen tanıma uygun çalışır hale getirilmesi için kontrollerin kodlanması
public class XmlFormatter : PrettyFormatProvider
{
    private XmlValidator _validator;
    private XmlSchemaValidator _schemaValidator; // (1) <-- !!!

    public XmlFormatter(string xsd) // (2) <-- !!!
    {
        if (String.IsNullOrWhiteSpace(xsd))
            _validator = new XmlValidator();
        else
            _schemaValidator = new XmlSchemaValidator(xsd);
    }

    public override string Format(string input) // (3) <-- !!!
    {
        if (_validator != null && _validator.IsValid(input))
            throw new ValidationException();

        if (_schemaValidator != null && _schemaValidator.IsValid(input))
            throw new ValidationException();
        
        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

Bu hali ile XmlFormatter sınıfı benzer işlev sunan iki ayrı denetleme sınıfını kullanmak için oldukça fazla kontrol içerir. Bu kontrollerden kurtulmak ve XmlFormatter sınıfını hafifletmek için denteleme işlevini bir kontrat ile genelleştirmeliyiz. Bu amaçla aşağıdaki PrettyFormatValidator soyut kontrat sınıfını oluşturalım.

public abstract class PrettyFormatValidator
{
    public abstract bool IsValid(string input);
}

PrettyFormatValidator‘ı artık XmlValidator ve XmlSchemaValidator sınıflarının atası olarak kullanabiliriz.

public class XmlValidator:PrettyFormatValidator
{
    public override bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

public class XmlSchemaValidator:PrettyFormatValidator
{
    private string _xsdSchema;
    public XmlSchemaValidator(string xsd)
    {
        _xsdSchema = xsd;
    }

    public override bool IsValid(string input)
    {
        // XSD şemaya göre kural denetimini yap
        return true;
    }
}

Bu düzenlemelerden sonra XmlFormatter sınıfına parametre olarak PrettyFormatValidator tipinden nesne alan yapıcı fonksiyon (constructor) ekleyip Format metodunu da PrettyFormatValidator sınıfının IsValid metodunu kullanacak şekilde düzenleyebiliriz.

public class XmlFormatter : PrettyFormatProvider
{
    private PrettyFormatValidator _validator;

    public XmlFormatter(PrettyFormatValidator validator)
    {
        _validator = validator;
    }

    public override string Format(string input)
    {

        if (_validator.IsValid(input))
            throw new ValidationException();
        
        // Formatlama işlemini yap
        return "formatlanmış metin!";
    }
}

Yukarıdaki düzenlemeler ile XmlFormatter sınıfını, çalışma anında ata sınıfın yerine türetilmiş sınıflardan nesneleri kullanarak ilgili işlevlere erişebilir hale getirdik.

Interface Segregation (Özelleşmiş Kontrat/Arayüz Ayrımı)

Genel kullanım amaçlı tek bir kontrat yerine daha özelleşmiş birden çok kontrat oluşturmayı tercih etmeliyiz.

Kural denetimi yapmak için kullandığımız XmlValidator, XmlSchemaValidator, JsonValidator ve HtmlValidator sınıflarının herhangi birinde XmlSchemaValidator sınıfındaki gibi şema kullanımına izin vermek için PrettyFormatValidator soyut ata sınıfı yerine aşağıdaki gibi bir IPrettyFormatValidator arayüzü (interface) tanımlayalım.

public interface IPrettyFormatValidator
{
    string Schema { get; set; }
    bool IsValid(string input);
}

Arayüz tanımını yaptıktan sonra denetim sınıflarımızı,

  1. Hepsi IPrettyFormatValidator arayüzünden türetelim ve
  2. Hepsine Schema özelliğini ekleyelim
public class JsonValidator:IPrettyFormatValidator
{
    public string Schema { get; set; }
    public bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

public class HtmlValidator:IPrettyFormatValidator
{
    public string Schema { get; set; }
    public bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

public class XmlValidator:IPrettyFormatValidator
{
    public string Schema { get; set; }
    public bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

public class XmlSchemaValidator:IPrettyFormatValidator
{
    public string Schema { get; set; }

    public bool IsValid(string input)
    {
        // XSD şemaya göre kural denetimini yap
        return true;
    }
}

Bu düzenlemedeki temel sorun, şema denetimi yapmayacakları halde JsonValidator, HtmlValidator ve XmlValidatorsınıflarının da Schema özelliğine sahip olma zorunluluğudur. Denetleme sınıflarımızı kontrat tasarımından kaynaklanan bu zorunluluktan kurtarmak için IPrettyFormatValidator arayüzündeki Schema özelliğini daha özelleşmiş bir arayüz olan IPrettyFormatSchemaValidator arayüzüne taşıyabiliriz.

public interface IPrettyFormatValidator
{
    bool IsValid(string input);
}
public interface IPrettyFormatSchemaValidator:IPrettyFormatValidator
{
    string Schema { get; set; }
}

Denetleme sınıflarımızı ihtiyaçlarına göre bu iki arayüzden birini kullanacak şekilde aşağıdaki gibi yeniden düzenleyelim.

public class XmlValidator:IPrettyFormatValidator
{
    public bool IsValid(string input)
    {
        // Kural denetimini yap
        return true;
    }
}

public class XmlSchemaValidator:IPrettyFormatSchemaValidator
{
    public string Schema { get; set; }

    public bool IsValid(string input)
    {
        // XSD şemaya göre kural denetimini yap
        return true;
    }
}

XmlValidator sınıfı IPrettyFormatValidator arayüzünden türetiliyor ve ihtiyacı olmayan Schema özelliğini artık barındırmıyor. Ancak, XmlSchemaValidator sınıfı daha özelleşmiş bir arayüz olan IPrettyFormatSchemaValidator arayüzünden türetiliyor ve ihtiyaç duyduğu Schema özelliğini kontrata uygun olarak barındırıyor.

Dependency Inversion (Bağımlılıkların Tersyüz Edilmesi)

Katmanlı mimarilerde üst seviye modüller alt seviyedeki modüllere doğruda bağımlı olmamalıdır.

Open/closed prensibini açıklarken kullandığımız aşağıdaki örnek kod parçasında yer alan PrettyFormattersınıfının web uygulama katmanında yer alan bir sınıf olduğunu JsonFormatter ve HtmlFormatter sınıflarının ise kütüphane olarak kullanılan daha alt katmanlardan birinde yer aldığını varsayalım.

public class PrettyFormatter
{
    // Format tipleri
    public enum FormatTypes
    {
        Json,
        Html
    }

    // Formatlama işlemini yapacak olan nesneler
    private JsonFormatter _jsonFormatter = new JsonFormatter();
    private HtmlFormatter _htmlFormatter = new HtmlFormatter();

    // Formatlama işlemini yapan metod
    public string Format(FormatTypes inputType, string input)
    {
        switch (inputType)
        {
            case FormatTypes.Json:
                return _jsonFormatter.Format(input);
            case FormatTypes.Html:
                return _htmlFormatter.Format(input);
            default:
                throw new Exception("Desteklenmeyen format tipi!");
        }
    }
}

Yukarıdaki hali ile üst katmandaki PrettyFormatter sınıfı alt katmanda yer alan JsonFormatter ve HtmlFormatter sınıflarından nesneleri kendisi oluşturmalıdır yani PrettyFormatter sınıfı sorumluluğunu yerine getirmek için alt katmanlardaki sınıflara bağımlıdır. Bu bağımlılığı ortadan kaldırmak için JsonFormatter ve HtmlFormatter sınıflarını PrettyFormatProvider sınıfından türeterek PrettyFormatter sınıfına kurucu fonksiyonu (constructor) vasıtası ile dışarıdan verilmesini (constructor injection) sağlayabiliriz.

public class PrettyFormatter
{
    private PrettyFormatProvider _provider;
    public PrettyFormatter(PrettyFormatProvider provider)
    {
        _provider = provider;
    }

    // Formatlama işlemini yapan metod
    public string Format(string input)
    {
        return _provider.Format(input);
    }
}

NOT: PrettyFormatter sınıfının kodunun sizde olmadığını ve bu sınıfta dependency inversion prensibine uyulmadığını varsayın. Bu durumda PrettyFormatter sınıfını kendi geliştireceğiniz JavaScriptFormatter benzeri yeni bir formatlayıcıyı kullanmasını sağlamanız mümkün olmazdı.

Son Söz

İyi bir yazılım geliştirici olmak için SOLID prensiplerini mutlaka ama mutlaka öğrenmelisiniz. SOLID prensipleri sadece OOP (Nesne Yönelimli Programlama) alanında değil diğer bir çok yaklaşım ve alanda da dolaylı olarak kullanılabilir veya farklı bakış açılarını harekete geçirebilir. Tüm OOP tasarımlarınız ve kodunuz SOLID prensiplerine her an yüzde yüz uygun olmayabilir. Ancak, özellikle yeni işlevler eklerken veya hata düzeltmeleri yaparken mutlaka ama mutlaka refactoring (işlevi bozmadan yeniden yazma) pratikleri ve SOLID bilgisi ile kodunuzun SOLID prensiplerine uygunluğunu arttırabilirsiniz.


Bu yazıyı beğendiyseniz Twitter’da takipçilerinizle paylaşabilir veya beni Twitter’da takip edebilirsiniz.


Örnek Kod

using System;

namespace Solid
{
    class Program
    {
        static void Main(string[] args)
        {
            var jsonFormatter = new JsonFormatter();

            PrettyFormatter formatter = new PrettyFormatter(jsonFormatter);
            var formattedText = formatter
             .Format(@"{""id"":1,""ad"":""Ali"",""soyad"":""Özgür""}");
        }
    }

    public class PrettyFormatter
    {
        private PrettyFormatProvider _provider;
        public PrettyFormatter(PrettyFormatProvider provider)
        {
            _provider = provider;
        }

        // Formatlama işlemini yapan metod
        public string Format(string input)
        {
            return _provider.Format(input);
        }
    }

    #region Providers
    public abstract class PrettyFormatProvider
    {
        public abstract string Format(string input);
    }

    public class JsonFormatter : PrettyFormatProvider
    {
        private IPrettyFormatValidator _validator;
        public JsonFormatter(IPrettyFormatValidator validator)
        {
            _validator = validator;
        }


        public override string Format(string input)
        {

            if (!_validator.IsValid(input))
                throw new ValidationException();

            // Formatlama işlemini yap
            return "formatlanmış metin!";
        }
    }

    public class HtmlFormatter : PrettyFormatProvider
    {
        private IPrettyFormatValidator _validator;
        public HtmlFormatter(IPrettyFormatValidator validator)
        {
            _validator = validator;
        }


        public override string Format(string input)
        {

            if (!_validator.IsValid(input))
                throw new ValidationException();

            // Formatlama işlemini yap
            return "formatlanmış metin!";
        }
    }

    public class XmlFormatter : PrettyFormatProvider
    {
        private IPrettyFormatValidator _validator;

        public XmlFormatter(IPrettyFormatValidator validator)
        {
            _validator = validator;
        }

        public override string Format(string input)
        {

            if (_validator.IsValid(input))
                throw new ValidationException();
            
            // Formatlama işlemini yap
            return "formatlanmış metin!";
        }
    }

    #endregion // Providers

    #region Validators
    public class ValidationException : ApplicationException { } 

    public interface IPrettyFormatValidator
    {
        bool IsValid(string input);
    }

    public interface IPrettyFormatSchemaValidator:IPrettyFormatValidator
    {
        string Schema { get; set; }
    }


    public class JsonValidator:IPrettyFormatValidator
    {
        public bool IsValid(string input)
        {
            // Kural denetimini yap
            return true;
        }
    }

    public class HtmlValidator:IPrettyFormatValidator
    {
        public bool IsValid(string input)
        {
            // Kural denetimini yap
            return true;
        }
    }

    public class XmlValidator:IPrettyFormatValidator
    {
        public bool IsValid(string input)
        {
            // Kural denetimini yap
            return true;
        }
    }

    public class XmlSchemaValidator:IPrettyFormatSchemaValidator
    {
        public string Schema { get; set; }

        public bool IsValid(string input)
        {
            // XSD şemaya göre kural denetimini yap
            return true;
        }
    }

    #endregion //Validators

}