Wzorzec projektowy do importu danych różnych typów źródeł i różnych typów miejsc docelowych

14

Muszę zaprojektować i zbudować skrypt importu (w języku C #), który może obsłużyć następujące elementy:

  • odczyt danych z różnych źródeł (XML, XSLX, CSV)
  • zweryfikuj dane
  • zapisz dane do różnych typów obiektów (klient, adres)

Dane będą pochodzić z wielu źródeł, ale źródło zawsze będzie miało jeden format importu (csv, xml, xslx). Formaty importu mogą się różnić w zależności od źródła. Nowe formaty importu mogą zostać dodane w przyszłości. Typy obiektów docelowych są zawsze takie same (klient, adres i kilka innych).

Zastanawiałem się nad użyciem leków generycznych i przeczytałem coś o fabrycznym wzorcu, ale jestem dość dużym noobem w tej dziedzinie, więc wszelkie porady są mile widziane.

Jaki jest odpowiedni wzorzec projektowy do rozwiązania tego problemu?

jao
źródło
Nie komplikuj.
NoChance 27.09.16

Odpowiedzi:

11

Przesadzasz z wymyślnymi koncepcjami, które były zbyt wcześnie. Generyczne - gdy zobaczysz skrzynkę, użyj ich, ale w przeciwnym razie nie martw się. Wzorzec fabryczny - jak dotąd zbyt duża elastyczność (i dodatkowe zamieszanie).

Nie komplikuj. Stosuj podstawowe praktyki.

  1. Spróbuj wyobrazić sobie wspólne rzeczy między czytaniem w formacie XML, czytaniem w pliku CSV. Rzeczy takie jak następny rekord, następny wiersz. Ponieważ można dodawać nowe formaty, spróbuj wyobrazić sobie podobieństwo, które miałby być określony format ze znanymi. Skorzystaj z tej podobieństwa i zdefiniuj „interfejs” lub umowę, której muszą przestrzegać wszystkie formaty. Chociaż przestrzegają wspólnej płaszczyzny, wszyscy mogą mieć swoje własne wewnętrzne reguły.

  2. Aby sprawdzić poprawność danych, spróbuj zapewnić sposób łatwego podłączenia nowych lub różnych bloków kodu weryfikatora. Ponownie spróbuj więc zdefiniować interfejs, w którym każdy walidator odpowiedzialny za określony rodzaj konstrukcji danych przestrzega umowy.

  3. Do tworzenia konstrukcji danych prawdopodobnie ograniczy cię ten, kto projektuje sugerowane obiekty wyjściowe bardziej niż cokolwiek innego. Spróbuj dowiedzieć się, jaki jest następny krok dla obiektów danych i czy istnieją jakieś optymalizacje, które możesz wykonać, znając ostateczne zastosowanie. Na przykład, jeśli wiesz, że obiekty będą używane w interaktywnej aplikacji, możesz pomóc twórcy tej aplikacji, dostarczając „podsumowania” lub liczby obiektów lub innych rodzajów pochodnych informacji.

Powiedziałbym, że większość z nich to wzorce szablonów lub wzorce strategii. Cały projekt byłby wzorem adaptera.

Andyz Smith
źródło
+1, szczególnie w pierwszym akapicie (i miło jest widzieć, że doszedłeś do tego samego wniosku co ja w ostatnim akapicie).
Doc Brown
Należy również pamiętać o architekturze całego projektu, aby dostosować jeden format do drugiego. Czy potrafisz sobie wyobrazić sytuację, w której ktoś mógłby wykorzystać tylko jedną jego część w innym projekcie? EG Może na rynku pojawia się nowy moduł sprawdzania poprawności danych, który działa tylko z serwerem SQL. Więc teraz po prostu chcesz przeczytać niestandardowy kod XML i umieścić w serwerze SQL, pomijając pozostałe kroki.
Andyz Smith
Aby to ułatwić, nie tylko elementy powinny mieć umowy wewnętrzne, do których się stosują, powinien istnieć zestaw umów określających interakcję między elementami .
Andyz Smith,
@AndyzSmith - Mam identyczny problem w kodzie. Zrozumiałem wszystko o twoim kodzie oprócz wzorca adaptera. Kiedy powiedziałeś, że cały projekt jest przykładem wzorca adaptera, możesz to zilustrować?
gansub
9

Oczywistą rzeczą jest zastosowanie wzorca strategii . Posiada ogólną klasę bazową ReadStrategyi dla każdego formatu wejściowego podklasą jak XmlReadStrategy, CSVReadStrategyitd. To pozwoli Ci zmienić przetwarzanie importu niezależnie od przetwarzania verfication i przetwarzania wyjściowego.

W zależności od szczegółów może być również możliwe zachowanie ogólnej części importu i wymiana tylko części przetwarzania danych wejściowych (na przykład odczyt jednego rekordu). Może to prowadzić do wzorca metody szablonu .

Doktor Brown
źródło
Czy to oznacza, że ​​używając wzorca strategii muszę stworzyć osobne metody konwersji obiektów (klienta, adresu) ze źródła do miejsca docelowego. Chciałbym przeczytać, przekonwertować i zweryfikować każdy obiekt i umieścić go na liście, aby można było później zapisać listę w bazie danych.
jao
@jao: cóż, jeśli ponownie przeczytasz moją odpowiedź, zobaczysz, że moją sugestią było stworzenie „ReadStrategy”, a nie „ConvertStrategy”. Musisz więc pisać tylko różne metody odczytu obiektów (lub jakakolwiek dodatkowa część procesu jest indywidualna dla określonego formatu pliku).
Doc Brown
7

Odpowiednim wzorcem dla narzędzia importującego, które może być konieczne w przyszłości, byłoby użycie MEF - możesz utrzymać niskie zużycie pamięci, ładując potrzebny konwerter w locie z leniwej listy, twórz import MEF, który jest ozdobiony atrybutami które pomagają wybrać odpowiedni konwerter dla importu, który próbujesz wykonać, i zapewniają łatwy sposób oddzielenia różnych klas importowania.

Każda część MEF może zostać zbudowana w celu spełnienia interfejsu importu za pomocą standardowych metod, które konwertują wiersz pliku importu na dane wyjściowe lub zastępują klasę podstawową za pomocą podstawowej funkcjonalności.

MEF to framework do tworzenia architektury wtyczek - jego wygląd i Visual Studio, wszystkie te piękne rozszerzenia VS są częściami MEF.

Aby zbudować aplikację MEF (Managed Extensability Framework), zacznij od dołączenia odwołania do System.ComponentModel.Composition

Zdefiniuj interfejsy, aby określić, co zrobi konwerter

public interface IImportConverter
{
    int UserId { set; }        
    bool Validate(byte[] fileData, string fileName, ImportType importType);
    ImportResult ImportData(byte[] fileData, string fileName, ImportType importType);
}

Można tego użyć dla wszystkich typów plików, które chcesz zaimportować.

Dodaj atrybuty do nowej klasy, które określają, co klasa będzie „eksportować”

[Export(typeof(IImportConverter))]
[MyImport(ImportType.Address, ImportFileType.CSV, "4eca4a5f-74e0")]
public class ImportCSVFormat1 : ImportCSV, IImportConverter
{
 ...interface methods...
}

Określiłoby to klasę, która będzie importować pliki CSV (określonego formatu: Format1) i ma niestandardowe atrybuty, które ustawiają metadane atrybutów eksportu MEF. Powtórz to dla każdego formatu lub typu pliku, który chcesz zaimportować. Możesz ustawić niestandardowe atrybuty za pomocą klasy takiej jak:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class ImportAttribute : ExportAttribute
{
    public ImportAttribute(ImportType importType, ImportFileType fileType, string customerUID)
        : base(typeof(IImportConverter))
    {
        ImportType = importType;
        FileType = fileType;
        CustomerUID = customerUID;
    }

    public ImportType ImportType { get; set; }
    public ImportFileType FileType { get; set; }
    public string CustomerUID { get; set; }
}

Aby faktycznie korzystać z konwerterów MEF, musisz zaimportować części MEF utworzone podczas uruchamiania kodu konwersji:

[ImportMany(AllowRecomposition = true)]
protected internal Lazy<IImportConverter, IImportMetadata>[] converters { get; set; }
AggregateCatalog catalog = new AggregateCatalog();

catalog zbiera części z folderu, domyślnie jest to lokalizacja aplikacji.

converters to leniwa lista importowanych części MEF

Następnie, gdy wiesz, jakiego rodzaju plik chcesz przekonwertować ( importFileTypei importType) pobierz konwerter z listy importowanych częściconverters

var tmpConverter = (from x in converters
                    where x.Metadata.FileType == importFileType
                    && x.Metadata.ImportType == importType 
                    && (x.Metadata.CustomerUID == import.ImportDataCustomer.CustomerUID)
                    select x).OrderByDescending(x => x.Metadata.CustomerUID).FirstOrDefault();

if (tmpConverter != null)
{
     var converter = (IImportConverter)tmpConverter.Value;
     result = converter.ImportData(import.ImportDataFile, import.ImportDataFileName, importType);
....
}

Wywołanie do converter.ImportDataużyje kodu w zaimportowanej klasie.

Może to wydawać się dużym kodem i może zająć trochę czasu, ale wszystko jest bardzo elastyczne, jeśli chodzi o dodawanie nowych typów konwerterów, a nawet pozwala ci dodawać nowe podczas działania.

Matt
źródło
Nie słyszałem wcześniej o MEF. Co to jest?
jao
2
@jao sprawdź link, aby uzyskać pełne wyjaśnienie. Dodano kilka przykładowych elementów MEF do mojej odpowiedzi.
Matt
1
To doskonały sposób na rozpoczęcie MEF. +1
paqogomez
MEF to technologia, a nie wzorzec projektowy. Nie -1ode mnie, ponieważ leżąca u podstaw idea nadal ma sens i opiera się na wzorcu strategii rządzonym przez IImportConverterinterfejs.
GETah,
0

Jaki jest odpowiedni wzorzec projektowy do rozwiązania tego problemu?

Idiomy C # wymagają do tego celu wbudowanego szkieletu serializacji. Adnotuj obiekty za pomocą metadanych, a następnie twórz instancje różnych serializatorów, które używają tych adnotacji do wyrywania danych w celu wprowadzenia ich do właściwej formy lub odwrotnie.

Formaty Xml, JSON i binarne są najczęstsze, ale nie zdziwiłbym się, gdyby inne istniały już w ładnej, spakowanej formie do konsumpcji.

Telastyn
źródło
Cóż, działa to dobrze, jeśli masz swobodę korzystania z własnego formatu pliku, ale myślę, że to podejście zawiedzie w przypadku złożonych, wstępnie zdefiniowanych formatów, takich jak XSLX, co oznacza pliki MS Excel w skompresowanym formacie XML.
Doc Brown
Mogę odwzorować wiersz pliku Excela na obiekt, ale musiałbym skopiować i dostosować tę metodę do czytników XML i CSV. I chciałbym zachować kod tak czysty, jak to możliwe ...
jao
@docBrown - Howso? Pod względem koncepcyjnym przekształcenie obiektu w serię komórek w programie Excel tak naprawdę nie różni się od przekształcenia go w dokument xml.
Telastyn
@Telastyn: mówisz, że możesz użyć wbudowanego frameworku serializacji .NET do odczytu formatu XLSX? Jeśli byłoby to prawdą, biblioteki takie jak Open XML SDK lub NPOI były przestarzałe.
Doc Brown
@docbrown: przepraszam, masz rację - ciągle zapominam, że nie ma wspólnej klasy bazowej serializatora, ponieważ jest to jedna z pierwszych rzeczy, które można zrobić w dowolnej bazie kodu, w której pracuję.
Telastyn