Pemrograman Berorientasi Aspek: Kapan mulai menggunakan kerangka kerja?

22

Saya baru saja menyaksikan pembicaraan oleh Greg Young ini memperingatkan orang-orang untuk KISS: Keep It Simple Stupid.

Salah satu hal yang ia menyarankan adalah bahwa untuk melakukan pemrograman berorientasi aspek, salah satu tidak tidak memerlukan kerangka kerja .

Dia mulai dengan membuat batasan yang kuat: bahwa semua metode mengambil satu, dan hanya satu, parameter (meskipun dia mengendurkan ini sedikit kemudian dengan menggunakan aplikasi parsial ).

Contoh yang diberikannya adalah mendefinisikan antarmuka:

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

Jika kita ingin mengeluarkan perintah:

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

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

Perintah diimplementasikan sebagai:

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);
    }
}

Untuk melakukan logging ke konsol, satu maka cukup implementasikan:

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);
    }
}

Kemudian, logging pra-perintah, layanan perintah, dan pasca-perintah logging kemudian hanya:

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

dan perintah dijalankan oleh:

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

Untuk melakukan ini, misalnya, PostSharp , orang akan membubuhi keterangan dengan CommandServicecara ini:

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

Dan kemudian harus mengimplementasikan logging di kelas atribut seperti:

[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 );
    }
}

Argumen yang digunakan Greg adalah bahwa koneksi dari atribut ke implementasi atribut adalah "terlalu banyak sihir" untuk dapat menjelaskan apa yang terjadi pada pengembang junior. Contoh awal adalah semua "kode adil" dan mudah dijelaskan.

Jadi, setelah penumpukan yang agak lama, pertanyaannya adalah: kapan Anda beralih dari pendekatan non-kerangka kerja Greg ke menggunakan sesuatu seperti PostSharp untuk AOP?

Peter K.
sumber
3
+1: Pasti pertanyaan yang bagus. Orang mungkin hanya mengatakan "... ketika Anda sudah mengerti solusinya tanpa itu."
Steven Evers
1
Mungkin saya tidak terbiasa dengan gaya, tetapi gagasan untuk menulis seluruh aplikasi seperti ini menurut saya benar-benar gila. Saya lebih suka menggunakan metode interseptor.
Aaronaught
@Aaronaught: Ya, itulah bagian dari mengapa saya ingin memposting di sini. Penjelasan Greg adalah bahwa konfigurasi sistem kemudian hanya menghubungkan IN NORMAL CODE semua bagian yang berbeda IConsumes. Daripada harus menggunakan XML eksternal atau antarmuka Lancar --- hal lain untuk dipelajari. Orang bisa berpendapat bahwa metodologi ini adalah "hal lain yang harus dipelajari" juga.
Peter K.
Saya masih tidak yakin saya mengerti motivasi; inti dari konsep-konsep seperti AOP adalah untuk dapat mengungkapkan keprihatinan secara deklaratif , yaitu melalui konfigurasi. Bagi saya ini hanya menciptakan kembali roda persegi. Bukan kritik terhadap Anda atau pertanyaan Anda, tetapi saya pikir satu-satunya jawaban yang masuk akal adalah "Saya tidak akan pernah menggunakan pendekatan Greg kecuali setiap opsi lain gagal."
Aaronaught
Bukannya itu mengganggu saya sama sekali, tetapi bukankah ini sedikit lebih merupakan pertanyaan Stack Overflow?
Rei Miyasaka

Jawaban:

17

Apakah ia mencoba menulis kerangka kerja AOP "langsung ke TDWTF"? Aku benar-benar masih belum tahu apa maksudnya. Segera setelah Anda mengatakan "Semua metode harus mengambil tepat satu parameter" maka Anda telah gagal, bukan? Pada tahap itu Anda berkata, OK ini memberlakukan beberapa kendala artifisial serius pada kemampuan saya untuk menulis perangkat lunak, mari kita letakkan ini sekarang sebelumnya, tiga bulan ke depan kita memiliki basis kode mimpi buruk yang lengkap untuk bekerja dengannya.

Dan tahukah Anda? Anda dapat menulis kerangka kerja logging berbasis IL sederhana yang digerakkan oleh atribut dengan cukup mudah dengan Mono.Cecil . (Mengujinya sedikit lebih rumit, tapi ...)

Oh dan IMO, jika Anda tidak menggunakan atribut, itu bukan AOP. Inti dari melakukan metode masuk / keluar kode logging pada tahap post processor adalah agar tidak mengacaukan dengan file kode Anda dan Anda tidak perlu memikirkannya saat Anda memperbaiki kode Anda; yang merupakan kekuatannya.

Semua Greg telah menunjukkan ada paradigma bodoh tetap itu.


sumber
6
+1 untuk menjaganya tetap bodoh. Mengingatkan saya pada kutipan terkenal Einstein: "buat semuanya sesederhana mungkin, tetapi tidak sesederhana itu."
Rei Miyasaka
FWIW, F # memiliki batasan yang sama, setiap metode mengambil paling banyak satu argumen.
R0MANARMY
1
let concat (x : string) y = x + y;; concat "Hello, " "World!";;sepertinya butuh dua argumen, apa yang saya lewatkan?
2
@The Mouth - apa yang sebenarnya terjadi adalah bahwa dengan concat "Hello, "Anda benar-benar membuat fungsi yang mengambil adil y, dan telah xditetapkan sebelumnya sebagai ikatan lokal menjadi "Halo,". Jika fungsi antara ini bisa dilihat, itu akan terlihat seperti let concat_x y = "Hello, " + y. Dan setelah itu, Anda menelepon concat_x "World!". Sintaks membuatnya kurang jelas, tetapi ini memungkinkan Anda "memanggang" fungsi baru - misalnya let printstrln = print "%s\n" ;; printstrln "woof",. Juga, bahkan jika Anda melakukan sesuatu seperti let f(x,y) = x + y, itu sebenarnya hanya satu tupel argumen.
Rei Miyasaka
1
Pertama kali saya melakukan pemrograman fungsional di Miranda kembali ke universitas, saya harus melihat F #, kedengarannya menarik.
8

Ya Tuhan, pria itu sangat kasar. Saya harap saya baru saja membaca kode di pertanyaan Anda alih-alih menonton pembicaraan itu.

Saya tidak berpikir saya akan pernah menggunakan pendekatan ini jika itu hanya demi menggunakan AOP. Greg bilang itu bagus untuk situasi sederhana. Inilah yang akan saya lakukan dalam situasi sederhana:

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

Ya, saya berhasil, saya menyingkirkan AOP sepenuhnya! Mengapa? Karena Anda tidak memerlukan AOP dalam situasi sederhana .

Dari sudut pandang pemrograman fungsional, hanya mengizinkan satu parameter per fungsi tidak benar-benar membuat saya takut. Meskipun demikian, ini sebenarnya bukan desain yang bekerja dengan baik dengan C # - dan bertentangan dengan butiran bahasa Anda tidak KISS apapun.

Saya hanya akan menggunakan pendekatan ini jika perlu untuk membuat model perintah untuk memulai, misalnya jika saya perlu tumpukan undo atau jika saya bekerja dengan Perintah WPF .

Kalau tidak, saya hanya akan menggunakan kerangka kerja atau refleksi. PostSharp bahkan bekerja di Silverlight dan Compact Framework - jadi apa yang dia sebut "sihir" benar-benar tidak ajaib sama sekali .

Saya juga tidak setuju dengan menghindari kerangka kerja demi bisa menjelaskan hal-hal kepada junior. Tidak ada gunanya bagi mereka. Jika Greg memperlakukan juniornya dengan cara yang dia sarankan untuk diperlakukan, seperti orang bodoh, maka saya curiga bahwa pengembang seniornya juga tidak terlalu hebat, karena mereka mungkin belum diberi banyak kesempatan untuk belajar apa pun selama mereka tahun junior.

Rei Miyasaka
sumber
5

Saya melakukan studi independen di perguruan tinggi di AOP. Saya benar-benar menulis makalah tentang pendekatan untuk memodelkan AOP dengan plug-in Eclipse. Itu sebenarnya agak tidak relevan kurasa. Poin kuncinya adalah 1) Saya masih muda dan tidak berpengalaman dan 2) Saya bekerja dengan AspectJ. Saya dapat memberitahu Anda bahwa "keajaiban" dari sebagian besar kerangka kerja AOP tidak terlalu rumit. Saya benar-benar bekerja pada sebuah proyek sekitar waktu yang sama yang mencoba melakukan pendekatan parameter tunggal menggunakan hashtable. IMO, pendekatan parameter tunggal benar-benar merupakan kerangka kerja dan invasif. Bahkan pada posting ini, saya menghabiskan lebih banyak waktu untuk mencoba memahami pendekatan parameter tunggal daripada saya meninjau pendekatan deklaratif. Saya akan menambahkan peringatan bahwa saya belum menonton film, jadi "keajaiban" dari pendekatan ini mungkin dalam penggunaan aplikasi parsial.

Saya pikir Greg menjawab pertanyaan Anda. Anda harus beralih ke pendekatan ini ketika Anda berpikir Anda berada dalam situasi di mana Anda menghabiskan banyak waktu untuk menjelaskan kerangka kerja AOP kepada pengembang junior Anda. IMO, jika Anda berada di kapal ini, Anda mungkin mempekerjakan pengembang junior yang salah. Saya tidak percaya AOP membutuhkan pendekatan deklaratif, tetapi bagi saya, itu jauh lebih jelas dan non-invasif dari perspektif desain.

kakridge
sumber
+1 untuk "Saya menghabiskan lebih banyak waktu mencoba memahami pendekatan parameter tunggal daripada saya meninjau pendekatan deklaratif." Saya menemukan IConsume<T>contoh yang terlalu rumit untuk apa yang dicapai.
Scott Whitlock
4

Kecuali jika saya melewatkan sesuatu kode yang telah Anda tunjukkan adalah pola desain 'rantai tanggung jawab' yang bagus jika Anda perlu memasang serangkaian tindakan pada suatu objek (seperti perintah melalui serangkaian penangan perintah) di runtime.

AOP menggunakan PostSharp baik jika Anda tahu pada waktu kompilasi perilaku apa yang ingin Anda tambahkan. Kode PostSharp menenun cukup banyak berarti tidak ada overhead run-time dan menjaga kode tetap sangat bersih (terutama ketika Anda mulai menggunakan hal-hal seperti aspek multicast). Saya tidak berpikir dasar penggunaan PostSharp sangat rumit untuk dijelaskan. Kelemahan dari PostSharp adalah bahwa ia meningkatkan waktu kompilasi secara signifikan.

Saya menggunakan kedua teknik dalam kode produksi dan meskipun ada beberapa tumpang tindih di mana mereka dapat diterapkan, saya pikir sebagian besar mereka benar-benar ditujukan pada skenario yang berbeda.

FinnNk
sumber
4

Mengenai alternatifnya - pernah ke sana, melakukan itu. Tidak ada yang sebanding dengan keterbacaan atribut satu baris.

Berikan ceramah singkat kepada orang-orang baru yang menjelaskan kepada mereka bagaimana hal-hal bekerja di AOP.

Danny Varod
sumber
4

Apa yang dijelaskan Greg benar-benar masuk akal. Dan ada keindahan di dalamnya juga. Konsep ini berlaku dalam paradigma yang berbeda dari orientasi objek murni. Ini lebih merupakan pendekatan prosedural atau pendekatan desain berorientasi aliran. Jadi jika Anda bekerja dengan kode lawas akan sangat sulit untuk menerapkan konsep ini karena banyak refactoring mungkin diperlukan.

Saya akan mencoba memberikan contoh lain. Mungkin tidak sempurna tetapi saya harap ini membuat poinnya lebih jelas.

Jadi kami memiliki layanan produk yang menggunakan repositori (dalam hal ini kami akan menggunakan rintisan). Layanan akan mendapatkan daftar produk.

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 }
        };
    }
}

Tentunya Anda juga bisa melewatkan antarmuka ke layanan.

Selanjutnya kami ingin menampilkan daftar produk dalam tampilan. Oleh karena itu kita memerlukan sebuah antarmuka

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

dan perintah yang menyimpan daftar produk

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

dan tampilan

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

Sekarang kita membutuhkan beberapa kode yang mengeksekusi semua ini. Ini akan kita lakukan di kelas yang disebut Aplikasi. Metode Run () adalah metode integrasi yang tidak mengandung atau setidaknya sedikit logika bisnis. Ketergantungan disuntikkan ke konstruktor sebagai metode.

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);
    }
}

Akhirnya kami membuat aplikasi dengan metode utama.

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();
}

Sekarang yang keren adalah kita bisa menambahkan aspek seperti pencatatan atau penanganan pengecualian tanpa menyentuh kode yang ada dan tanpa kerangka kerja atau anotasi. Untuk penanganan pengecualian misalnya, kami hanya menambahkan kelas baru:

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);
        }
    }
}

Dan kemudian kita pasang bersama selama komposisi di titik masuk aplikasi. kita bahkan tidak perlu menyentuh kode di kelas Aplikasi. Kami hanya mengganti satu baris:

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

Jadi untuk melanjutkan: Ketika kita memiliki desain berorientasi aliran, kita dapat menambahkan aspek dengan menambahkan fungsionalitas di dalam kelas baru. Maka kita harus mengubah satu baris dalam metode komposisi dan hanya itu.

Jadi saya pikir jawaban untuk pertanyaan Anda adalah bahwa Anda tidak dapat dengan mudah beralih dari satu pendekatan ke yang lain, tetapi Anda harus memutuskan jenis pendekatan arsitektural yang akan Anda gunakan dalam proyek Anda.

sunting: Sebenarnya saya baru menyadari bahwa pola aplikasi parsial yang digunakan dengan layanan produk membuat segalanya sedikit lebih rumit. Kita perlu membungkus kelas lain di sekitar metode layanan produk untuk dapat menambahkan aspek di sini juga. Bisa jadi sesuatu seperti ini:

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();
}

Komposisi kemudian harus diubah seperti ini:

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);
leifbattermann
sumber