Boy Yönelimli Programlama: Bir çerçeve ne zaman kullanılmaya başlanır?


22

Önce izlediğim bu konuşmayı tarafından Greg Young KISS uyarı insanlar: It Simple Stupid tutun.

O önerdi şeylerden biri biri, yani boy odaklı programlama yapmaktır yok değil bir çerçeve ihtiyaç .

Güçlü bir kısıtlama yaparak başlıyor: tüm yöntemlerin bir ve yalnızca bir parametre alması (bunu biraz sonra kısmi uygulama kullanarak gevşetmesine rağmen ).

Verdiği örnek bir arayüz tanımlamaktır:

public interface IConsumes<T>
{
    void Consume(T message);
}

Bir komut vermek istiyorsak:

public class Command
{
    public string SomeInformation;
    public int ID;

    public override string ToString()
    {
       return ID + " : " + SomeInformation + Environment.NewLine;
    }
}

Komut şu şekilde uygulanır:

public class CommandService : IConsumes<Command>
{
    private IConsumes<Command> _next;

    public CommandService(IConsumes<Command> cmd = null)
    {
        _next = cmd;
    }
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
        if (_next != null)
            _next.Consume(message);
    }
}

Konsola giriş yapmak için, bir tanesi sadece uygular:

public class Logger<T> : IConsumes<T>
{
    private readonly IConsumes<T> _next;

    public Logger(IConsumes<T> next)
    {
        _next = next;
    }
    public void Consume(T message)
    {
        Log(message);
        if (_next != null)
            _next.Consume(message);
    }

    private void Log(T message)
    {
        Console.WriteLine(message);
    }
}

Ardından, komut öncesi günlüğü, komut servisi ve komut sonrası günlüğü yalnızca:

var log1 = new Logger<Command>(null);
var svr  = new CommandService(log);
var startOfChain = new Logger<Command>(svr);

ve komut aşağıdakiler tarafından yürütülür:

var cmd = new Command();
startOfChain.Consume(cmd);

Bunu yapmak için, örneğin, PostSharp , biri CommandServiceşu şekilde açıklamalıdır :

public class CommandService : IConsumes<Command>
{
    [Trace]
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
    }
}

Daha sonra, günlüğü şöyle bir özellik sınıfında uygulamak zorundasınız:

[Serializable]
public class TraceAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Entered!" );   
    }

    public override void OnSuccess( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Exited!" );
    }

    public override void OnException( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : EX : " + args.Exception.Message );
    }
}

Greg'in kullandığı argüman, nitelikten özniteliğin uygulanmasına olan bağlantının, küçük bir geliştiriciye neler olduğunu açıklayabilmek için "çok fazla büyü" olduğu şeklindedir. İlk örnek tüm "sadece kod" ve kolayca açıklanır.

Öyleyse, bu uzun soluklu birikimden sonra soru şu: Greg'in çerçeve dışı yaklaşımından AOP için PostSharp gibi bir şey kullanmaya ne zaman geçiyorsunuz?


3
+1: Kesinlikle iyi bir soru. Birisi basitçe "... çözümü onsuz zaten anladığınızda" diyebilir.
Steven Evers

1
Belki de sadece stile alışkınım, ama böyle bir uygulamanın tamamını yazma fikri beni çok delice vuruyor. Bir yöntem önleyici kullanmayı tercih ederim.
Aaron,

@Aaronaught: Evet, bu yüzden buraya göndermek istedim bir parçası. Greg'in açıklaması, sistem konfigürasyonunun sadece tüm NORMAL KOD IN tüm farklı IConsumesparçaları birbirine bağladığı yönünde . Harici XML veya biraz Akıcı arayüz kullanmak zorunda kalmaktansa --- öğrenmesi gereken bir şey daha var. Biri de bu metodolojinin “öğrenilecek başka bir şey” olduğu iddia edilebilir.
Peter K.

Motivasyonu anladığımdan hala emin değilim; AOP gibi kavramların özü, endişeleri bildirimsel olarak , yani yapılandırma yoluyla ifade edebilmektir . Bana göre bu sadece kare tekerleği yeniden icat ediyor. Sizin veya sorunuzun eleştirisi değil, tek mantıklı cevabın "diğer her seçenek başarısız olmadıkça Greg'in yaklaşımını asla kullanmayacağım" olduğunu düşünüyorum.
Aaron,

Bu beni hiç rahatsız etmiyor, ama bu bir Stack Overflow sorusundan biraz daha fazlası olmaz mıydı?
Rei Miyasaka

Yanıtlar:


17

AOP çerçevesini "doğrudan TDWTF'ye" yazmaya mı çalışıyor? Cidden onun amacının ne olduğu hakkında hiçbir fikrim yok. "Tüm yöntemler tam olarak bir parametre almalı" deyince başarısız oldunuz değil mi? Söylediğin aşamada, tamam, bu benim yazılım yazma yeteneğime ciddi birtakım yapay kısıtlamalar getiriyor, hadi bunu daha önce bırakalım, çalışacağımız tam bir kabus kod üssünün olduğu satırdan üç ay önce.

Ve ne biliyor musun? Mono.Cecil ile basit bir öznitelik odaklı IL tabanlı günlük kaydı çerçevesini kolayca yazabilirsiniz . (test etmek biraz daha karmaşık, ama ...)

Oh ve IMO, özellikleri kullanmıyorsanız, AOP değildir. İşlem sonrası aşamada yöntem giriş / çıkış kodunu yapmanın asıl amacı, kod dosyalarınızı etkilememesi ve kodunuzu yeniden gözden geçirirken düşünmeniz gerekmez; Bu ise onun gücü.

Greg'in bütün gösterdiği aptalca aptal paradigmayı koruduğu.


6
Aptal aptal tutmak için +1. Bana Einstein'ın ünlü sözünü hatırlatıyor: "her şeyi mümkün olduğunca basitleştir, ancak daha basit hale getirme."
Rei Miyasaka

FWIW, F # aynı kısıtlamaya sahip, her yöntem en fazla bir argüman alıyor.
R0MANARMY

1
let concat (x : string) y = x + y;; concat "Hello, " "World!";;iki argüman alıyor gibi görünüyor, neyi özlüyorum?

2
@ The Mouth - aslında olan şudur; concat "Hello, "aslında siz sadece "Merhaba" olmak için yerel bir bağlayıcı olarak tanımlanmış yve xönceden tanımlanmış bir işlev yaratıyorsunuz . Bu ara işlev görülebilseydi, buna benzer bir şey olurdu let concat_x y = "Hello, " + y. Ve sonra bunu takip ediyorsun concat_x "World!". Sözdizimi onu daha az belirgin hale getirir, ancak bu yeni işlevleri "pişirmenize" olanak tanır - örneğin let printstrln = print "%s\n" ;; printstrln "woof",. Ayrıca, böyle bir şey yapsanız bile let f(x,y) = x + y, aslında sadece bir tek tartışma var.
Rei Miyasaka,

1
İşlevsel bir programlama yaptım, üniversitedeyken Miranda’daydı, F # 'a bir göz atmam gerekiyor, kulağa ilginç geliyor.

8

Tanrım, bu adam dayanılmaz derecede aşındırıcı. Keşke bu konuşmayı izlemek yerine, sorunuzdaki kodu okuyabilseydim.

Bu yaklaşımı yalnızca AOP kullanmak uğruna kullanırsam kullanacağımı sanmıyorum. Greg basit durumlar için iyi olduğunu söylüyor. İşte basit bir durumda ne yaparım:

public void DeactivateInventoryItem(CommandServices cs, Guid item, string reason)
{
    cs.Log.Write("Deactivated: {0} ({1})", item, reason);
    repo.Deactivate(item, reason);
}

Evet, yaptım, tamamen AOP'dan kurtuldum! Niye ya? Çünkü basit durumlarda AOP'ye ihtiyacınız yok .

İşlevsel programlama açısından bakıldığında, işlev başına yalnızca bir parametreye izin vermek beni gerçekten korkutmuyor. Bununla birlikte, bu gerçekten C # ile iyi çalışan bir tasarım değildir - ve dilinizin tanelerine karşı çıkmak hiçbir şeyi KISS yapmaz.

Bu yaklaşımı yalnızca başlangıç ​​için bir komut modeli yapmak gerektiğinde, örneğin bir geri alma yığınına ihtiyacım olduğunda ya da WPF Komutlarıyla çalışıyor olsaydım kullanırdım .

Aksi takdirde, sadece bir çerçeve ya da biraz yansıma kullanırdım. O "sihirli" dediği gerçekten büyülü değil - PostSharp bile Silverlight ve Compact Framework çalışan hiç .

Ayrıca, gençlere bir şeyler açıklayabilmek adına çerçevelerden kaçınmak konusunda hemfikir değilim. Onları hiç iyi yapmıyor. Greg, gençlerine kalın kafatasındaki salaklar gibi davranılmasını önerdiği gibi davranırsa, o zaman kıdemli geliştiricilerin de çok büyük olmadıklarından şüpheleniyorum, çünkü muhtemelen kendileri sırasında hiçbir şey öğrenme fırsatı bulamadılar. Küçük yıllar.


5

Üniversitede AOP konusunda bağımsız bir çalışma yaptım. Aslında Eclipse eklentisi ile AOP modeline yönelik bir yaklaşım hakkında bir yazı yazdım. Bu aslında sanırım biraz alakasız. Kilit noktalar 1) Genç ve deneyimsizdim ve 2) AspectJ ile çalışıyordum. AOP çerçevelerinin çoğunun "sihir" in o kadar karmaşık olmadığını söyleyebilirim. Aslında, bir karma tablo kullanarak tek bir parametre yaklaşımı yapmaya çalışan bir proje üzerinde çalıştım. IMO, tek parametre yaklaşımı gerçekten bir çerçeve ve istilacı. Bu görevde bile, tek değişkenli yaklaşımı anlamaya çalışmak için, bildirimsel yaklaşımı incelemekten daha fazla zaman harcadım. Filmi izlememiş olduğum bir uyarı ekleyeceğim, bu yüzden bu yaklaşımın “büyüsü” kısmi uygulamaların kullanımında olabilir.

Bence Greg sorunuzu cevapladı. AOP çerçevelerini küçük geliştiricilere açıklamak için çok fazla zaman harcadığınız bir durumda olduğunuzu düşünüyorsanız, bu yaklaşıma geçmelisiniz. IMO, eğer bu teknedeyseniz, muhtemelen yanlış küçük geliştiricileri işe alıyorsunuzdur. AOP'nin bildirimsel bir yaklaşım gerektirdiğine inanmıyorum, ancak benim için tasarım açısından çok daha açık ve müdahalesiz bir durum.


+1 "Tek parametre yaklaşımını anlamaya çalışırken, bildirimsel yaklaşımı incelemekten daha fazla zaman harcadım" IConsume<T>Başarısız olan için aşırı karmaşık bir örnek buldum .
Scott Whitlock

4

Bir şeyi kaçırmıyorsam, gösterdiğiniz kod, 'bir dizi komut işleyicisinden geçen komutlar gibi) nesneye bir dizi eylem bağlamanız gerektiğinde harika olan' sorumluluk zinciri 'tasarım desenidir. Çalışma zamanı.

Derleme zamanında eklemek istediğiniz davranışın ne olacağını biliyorsanız, PostSharp kullanarak AOP iyidir. PostSharp'ın hemen hemen dokuması kodu, sıfır çalışma zamanı ek yükü olduğu anlamına gelir ve kodu çok temiz tutar (özellikle çok noktaya yayın yönleri gibi şeyler kullanmaya başladığınızda). PostSharp'ın temel kullanımının özellikle açıklamak için karmaşık olduğunu düşünmüyorum. PostSharp'ın dezavantajı, derleme zamanlarını önemli ölçüde arttırmasıdır.

Her iki tekniği de üretim kodunda kullanıyorum ve uygulanabilecekleri yerlerde çakışma olmasına rağmen, çoğunlukla farklı senaryolar hedeflediklerini düşünüyorum.


4

Alternatifiyle ilgili olarak - orada bulundum, bunu yaptım. Hiçbir şey bir satırlık bir özniteliğin okunabilirliği ile karşılaştırılamaz.

AOP'da işlerin nasıl yürüdüğünü açıklayan yeni çocuklara kısa bir ders verin.


4

Greg'in tarif ettiği şey kesinlikle mantıklı. Ve içinde de güzellik var. Kavram, saf nesne yönelmesinden farklı bir paradigmada uygulanabilir. Daha çok prosedürel bir yaklaşım veya akış odaklı bir tasarım yaklaşımı. Bu nedenle, eski kodla çalışıyorsanız bu konsepti uygulamak oldukça zor olacaktır, çünkü birçok yeniden düzenleme gerekebilir.

Başka bir örnek vermeye çalışacağım. Belki de mükemmel değil ama umarım meseleyi daha açık hale getirir.

Bu yüzden bir depo kullanan bir ürün hizmetimiz var (bu durumda bir saplama kullanacağız). Hizmet bir ürün listesi alacak.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public override string ToString() { return String.Format("{0}, {1}", Name, Price); }
}

public static class ProductService
{
    public static IEnumerable<Product> GetAllProducts(ProductRepositoryStub repository)
    {
        return repository.GetAll();
    }
}

public class ProductRepositoryStub
{
    public ProductRepositoryStub(string connStr) {}

    public IEnumerable<Product> GetAll()
    {
        return new List<Product>
        {
            new Product {Name = "Cd Player", Price = 49.99m},
            new Product {Name = "Yacht", Price = 2999999m }
        };
    }
}

Ders dışı hizmete bir arayüz de iletebilirsiniz.

Sonra bir görünümdeki ürünlerin listesini göstermek istiyoruz. Bu yüzden bir arayüze ihtiyacımız var

public interface Handles<T>
{
    void Handle(T message);
}

ve ürün listesini tutan bir komut

public class ShowProductsCommand
{
    public IEnumerable<Product> Products { get; set; }
}

ve manzara

public class View : Handles<ShowProductsCommand>
{
    public void Handle(ShowProductsCommand cmd)
    {
        cmd.Products.ToList().ForEach(x => Console.WriteLine(x.ToString()));
    }
}

Şimdi tüm bunları çalıştıran bir koda ihtiyacımız var. Bunu Uygulama adında bir sınıfta yapacağız. Run () yöntemi, hiç veya en az çok az iş mantığı içeren bütünleştirme yöntemidir. Bağımlılıklar yapıcıya yöntem olarak enjekte edilir.

public class Application
{
    private readonly Func<IEnumerable<Product>> _getAllProducts;
    private readonly Action<ShowProductsCommand> _showProducts;

    public Application(Func<IEnumerable<Product>> getAllProducts, Action<ShowProductsCommand> showProducts)
    {
        _getAllProducts = getAllProducts;
        _showProducts = showProducts;
    }

    public void Run()
    {
        var products = _getAllProducts();
        var cmd = new ShowProductsCommand { Products = products };
        _showProducts(cmd);
    }
}

Sonunda uygulamayı ana yöntemde oluştururuz.

static void Main(string[] args)
{
    // composition
    Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
    Action<ShowProductsCommand> showProducts = (x) => new View().Handle(x);
    var app = new Application(getAllProducts, showProducts);

    app.Run();
}

Şimdi en güzel yanı, mevcut koda dokunmadan ve bir çerçeve veya ek açıklama olmadan günlük tutma veya istisna işleme gibi yönleri ekleyebilmemizdir. İstisnaların ele alınması için, örneğin sadece yeni bir sınıf ekledik:

public class ExceptionHandler<T> : Handles<T>
{
    private readonly Handles<T> _next;

    public ExceptionHandler(Handles<T> next) { _next = next; }

    public void Handle(T message)
    {
        try
        {
            _next.Handle(message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Sonra kompozisyon sırasında uygulamanın giriş noktasındaki fişini birleştiriyoruz. Uygulama sınıfındaki koda bile dokunmamız gerekmiyor. Sadece bir satır değiştiririz:

Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);

Devam etmek için: Akış yönelimli bir tasarımımız olduğunda, işlevselliği yeni bir sınıf içine ekleyerek yönleri ekleyebiliriz. O zaman kompozisyon yönteminde bir satırı değiştirmeliyiz ve hepsi bu.

Bu nedenle, sorunuzun cevabını, bir yaklaşımdan diğerine kolayca geçemeyeceğiniz, ancak projenizde ne tür mimari bir yaklaşım izleyeceğinize karar vermeniz gerektiği kanısındayım.

düzenleme: Aslında sadece ürün servisi ile birlikte kullanılan kısmi uygulama modelinin işleri biraz daha karmaşık hale getirdiğini fark ettim. Buraya yönleri de ekleyebilmek için ürün servis yönteminin çevresine başka bir sınıf sarmamız gerekiyor. Böyle bir şey olabilir:

public class ProductQueries : Queries<IEnumerable<Product>>
{
    private readonly Func<IEnumerable<Product>> _query;

    public ProductQueries(Func<IEnumerable<Product>> query)
    {
        _query = query;
    }

    public IEnumerable<Product> Query()
    {
        return _query();
    }
}

public interface Queries<TResult>
{
    TResult Query();
}

Kompozisyon daha sonra böyle değiştirilmelidir:

Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
Func<IEnumerable<Product>> queryAllProducts = new ProductQueries(getAllProducts).Query;
Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);
var app = new Application(queryAllProducts, showProducts);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.