Avatar billede Droa Seniormester
10. oktober 2018 - 09:31 Der er 4 kommentarer

Abstraction og Concrete Biblioteker

Hej Eksperter.

Jeg fik et freelance job hvor en af de store krav var kunden ville have et Abstract Bibliotek til et projekt de havde hyret nogen lidt for underkvalificeret folk til og lave.

Jobbet betsod i og genskrive hele projektet til og være mere abstract og mere objektivt, så der eventuelt kunne være plugin support.

Allerede nu vil nogen af jer eksperter sikkert stoppe mig, og sige jeg nok heller ikke er kvalificeret, siden jeg spørger om simple spørgsmål som dette, men det er kunden klar over, siden jeg dog har haft jobs fra ham igennem 5 år har han dog lidt mere tillid til jeg nok skal overkomme de problemer jeg skulle komme på :)

Nu har jeg arbejdet meget med plugins til projekter før, dog har jeg aldrig haft krav om at lave abstract biblioteker før, dog virker det til og være noget der trender en del for tiden.

Når jeg snakker om abstraction biblioteker, snakker jeg om og extracte alle interfaces og abstracts ind i et seperat bibliotek, som ens concrete bibliotek så bruger som en dependency.

alle Plugins bliver så loaded fra et concrete, og danner hovedsageligt objekter der bruger en form for interface eller abstract fra ens abstract bibliotek.

giver god mening.

men jeg er dog lidt nysgerrig i den bedste måde og lave instances fra ens plugin.

siden jeg bruger Autofac burde det være ret let at bare registrere ens Plugins til et interface, og bruge factory method modellen.

men siden jeg aldrig rigtigt har arbejdet med Abstract Biblioteker før, ville jeg gerne høre om der måske var nogen gode rotiner man kunne læse op på til denne type job?

på forhånd tak.
Avatar billede arne_v Ekspert
10. oktober 2018 - 15:02 #1
Jeg mener ikke at bregrebet "abstrakt bibliotek" er almindeligt brugt, saa det bliver lidt sjusseri.

Jeg vil mene at det du skal bruge er:


public class SomeData
{
    public ... SomeProperty { get; set; }
    public ... SomeOtherProperty { get; set; }
}
public interface ISomeFunctionality
{
    public SomeData SomeMethod();
    public void SomeOtherMethod(SomeData data);
}


som bygges til Some.dll og:


internal class SomeFunctionalityXXX : ISomeFunctionality
{
    public SomeData SomeMethod()
    {
    }
    public void SomeOtherMethod(SomeData data)
    {
    }
}
public class SomeFunctionalityXXXFactory()
{
    public SomeFunctionality GetSomeFunctionality():
}


der byggers til Some.XXX.dll og saa er problemet kun hvordan du instantierer SomeFunctionalityXXXFactory og her er der forskellige muligheder:
* simpel reflection
* et DI framework (Spring, NInject eller andet)
* et af MS's plugin frameworks
Avatar billede Droa Seniormester
10. oktober 2018 - 17:02 #2
jeg har som udgangspunkt brugt DDD til og designe dette program, da det virker til og være den måde man har adgang til de forskellige plugins og biblioteker.

siden et plugin kun skal kunne designes efter et abstracts bibliotek, burde det også gøre det lettere og vedligeholde et enkelt plugin, da det ikke er afhængigt af andet.

jeg fik lige lavet et eksempel selv, som jeg tænker at min kode burde se ud hvis jeg skal overholde mine krav.


using System;
using System.Threading.Tasks;
using DDD.Abstracts;

/// <summary>
/// Base Program Program.exe
///
/// Base kender til Abstracts og alle plugins, so de kan frit blive brugt her.
/// </summary>
namespace DDD
{
    class Program
    {
        static object _consoleLock = new object();
        static IMessageMonitor smsmonitor = new DDD.Tools.SMSs.SMSMonitor();
        static IMessageMonitor emailmonitor = new DDD.Tools.Emails.EmailMonitor();
        static IMessageProcessor processor = new DDD.Tools.MessageProcessor();
        static void Main(string[] args)
        {
            smsmonitor.Message += OnMessage;
            emailmonitor.Message += OnMessage;
            smsmonitor.Listen();
            emailmonitor.Listen();
        }

        private static void OnMessage(object sender, MessageEventArgs e)
        {
            lock(_consoleLock){
                var message = e.Message;
                processor.Process(message);
            }
        }
    }
}

/// <summary>
/// (Producer) SMS Plugin SMS.Lib.dll
/// </summary>
namespace DDD.Tools.SMSs
{
    /// <summary>
    /// Process som tjekker extern api for nye sms'er og sender dem ind i systemet som et event
    /// </summary>
    public class SMSMonitor : IMessageMonitor
    {
        static Random rnd = new Random();
        public event EventHandler<MessageEventArgs> Message;

        /// <summary>
        /// Intern Methode til og Trigger Message Event
        /// </summary>
        private void TriggerMessage(SMS message){
            Message?.Invoke(this,new MessageEventArgs(new SMSAdapter(message)));
        }

        /// <summary>
        /// MOCK Privat Methode til og lave et tilfældigt dummy nummer
        /// </summary>
        private string CreateDanishNumber(){
            return $"+45 {string.Format("{0:00-000-000}",rnd.Next(10_00_00_00,99_99_99_99+1))}";
        }

        /// <summary>
        /// laver en random MOCK SMS
        /// </summary>
        private SMS CreateRandom(){

            string[] networks = new[] {"telia","tdc","sonofon"};

            return new SMS(networks[rnd.Next(networks.Length)],CreateDanishNumber(),CreateDanishNumber(),"Hello, this is a test SMS");
        }

        /// <summary>
        /// starter process til og lytte efter nye sms'er
        /// </summary>
        public void Listen(){
            Run();
        }

        /// <summary>
        /// henter noget MOCK og sender dem ud som events
        /// </summary>
        public void Run(){
            TriggerMessage(CreateRandom());
            TriggerMessage(CreateRandom());
        }
    }
    /// <summary>
    /// Model som kan holde data omkring en smsbesked
    /// Simplificeret
    /// </summary>
    public class SMS
    {
        public string Network {get;}
        public string FromNumber {get;}
        public string ToNumber {get;}
        public string Message {get;}

        public SMS(string network, string from, string to, string message){
            Network = network;
            FromNumber = from;
            ToNumber = to;
            Message = message;
        }
    }

    /// <summary>
    /// Adapter-wrapper som adapter SMS til en IMessage
    /// </summary>
    public class SMSAdapter : IMessage
    {
        private SMS _sms;
        public string From => _sms.FromNumber;

        public string To => _sms.ToNumber;

        public string Subject => "SMS Message";

        public string Body => _sms.Message;

        public string SourceType => "SMS";

        public SMSAdapter(SMS sms){
            _sms = sms;
        }
    }
}

/// <summary>
/// (Producer) SMS Plugin Email.Lib.dll
/// </summary>
namespace DDD.Tools.Emails
{
    /// <summary>
    /// Process som tjekker ekstern api for Emails, og sender dem ind i systemet som events
    /// </summary>
    public class EmailMonitor : IMessageMonitor
    {
        static Random rnd = new Random();
        public event EventHandler<MessageEventArgs> Message;

        /// <summary>
        /// Intern methode til og trigger et Message Event
        /// </summary>
        /// <param name="email"></param>
        private void TriggerMessage(Email email){
            Message?.Invoke(this,new MessageEventArgs(new EmailAdapter(email)));
        }

        /// <summary>
        /// Laver en radom MOCK email
        /// </summary>
        Email CreateRandom(){

            // Laver lidt MOCK data at vælge fra
            string[] users = new[]{"test","admin","root","webmarster"};
            string[] domains = new[]{"localhost","example.com","eksempel.dk"};
            string[] subjects = new[]{"All your base are belong to us","A Winner Is You!","Arrow To The Knee"};
            var emailto = users[rnd.Next(users.Length)]+"@"+domains[rnd.Next(domains.Length)];
            var emailfrom = users[rnd.Next(users.Length)]+"@"+domains[rnd.Next(domains.Length)];
            var subject = subjects[rnd.Next(subjects.Length)];
           
            return new Email(emailfrom,emailto,null,null,subject,"Hello, this is a test Email");
        }

        /// <summary>
        /// Henter noget MOCK data, og trigger dem igennem events
        /// </summary>
        public void Run(){
            TriggerMessage(CreateRandom());
            TriggerMessage(CreateRandom());
        }

        /// <summary>
        /// Starter med og lytte efter nye emails
        /// </summary>
        public void Listen()
        {
            Run();
        }
    }
    /// <summary>
    /// Simplificeret Email Model
    /// </summary>
    public class Email
    {
        public string From {get;}
        public string To {get;}
        public string CC {get;}
        public string BCC {get;}
        public string Subject {get;}
        public string Body {get;}

        public Email(string from, string to, string cc, string bcc, string subject, string body){
            From = from;
            To = to;
            CC = cc;
            BCC = bcc;
            Subject = subject;
            Body = body;
        }
    }

    /// <summary>
    /// Adapter-wrapper som adapter Email til IMessage
    /// </summary>
    public class EmailAdapter : IMessage
    {
        private Email _email;
        public string From => _email.From;

        public string To => _email.To;

        public string Subject => _email.Subject;

        public string Body => _email.Body;

        public string SourceType => "Email";

        public EmailAdapter(Email email){
            _email = email;
        }
    }
}

/// <summary>
/// (Consumer) IMessageProcessor Plugin Processor.Messages.Console.lib.dll
/// </summary>
namespace DDD.Tools
{
    /// <summary>
    /// Message Processor som skriver message ud til Consollen
    /// </summary>
    public class MessageProcessor : IMessageProcessor
    {
        public void Process(IMessage message)
        {
            Console.WriteLine("!New Message!");
            Console.WriteLine($"Agent: {message.SourceType}");
            Console.WriteLine($"From: {message.From}");
            Console.WriteLine($"To: {message.To}");
            Console.WriteLine($"Subject: {message.Subject}");
            Console.WriteLine(message.Body);
        }
    }
}

/// <summary>
/// Abstracts (abstracts.library.dll)
/// </summary>
namespace DDD.Abstracts
{
    /// <summary>
    /// er godt nok en klasse, men det virkede mest logisk og alligevel have den her
    /// </summary>
    public class MessageEventArgs
    {
        public IMessage Message {get;}
        public MessageEventArgs(IMessage message){
            Message = message;
        }
    }

    /// <summary>
    /// repræsentere en process som kan overvåge beskedder fra intern eller ekstern kilde, ved ny besked trigger den et event på sig selv, som andre kan registrere sig til
    /// </summary>
    public interface IMessageMonitor
    {
        /// <summary>
        /// Event som bliver triggered når der kommer en ny besked
        /// </summary>
        event EventHandler<MessageEventArgs> Message;

        /// <summary>
        /// Starter lytte process, burde nok være en async task, men det er jo kun et eksempel
        /// </summary>
        void Listen();
    }

    /// <summary>
    /// Model som indeholder en besked
    /// </summary>
    public interface IMessage
    {
        string From {get;}
        string To {get;}
        string Subject {get;}
        string Body {get;}

        string SourceType {get;}
    }
    /// <summary>
    /// Process som behandler en Besked
    /// </summary>
    public interface IMessageProcessor
    {
        void Process(IMessage message);
    }
}
Avatar billede arne_v Ekspert
17. oktober 2018 - 14:44 #3
Det er meget kode og jeg kan have overset noget, men jeg kan ikke rigtigt se noget plugin support.
Avatar billede Droa Seniormester
18. oktober 2018 - 08:46 #4
det var også kun en delvis kode, da jeg kunne set mit eksempel blev alt for langt, men jo der skulle så også være nogen Factory Methoder til mine typer og et DependencyInjection Factory til og initializere det hele.

Eksemplet gik mere ud på og rodde det hele i class Program, så man kunne adskille Design (Interface) for Concrete. så kun Program kender til Concrete, mens resten kun kender til Design (Interfaces).

den Initializerende del ville som sagt være hvor mine plugins, og andre Concretes skule findes i (Program)

det er lidt svært og simulere 3-4 biblioteker i en fil, men jeg prøvede :)
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester