Класса для обеспечения доступа к различным источникам данных


Я пытаюсь понять, как реализовать принципы инкапсуляции, наследования и полиморфизма, чтобы взломать этот код на логические части. Мой фон в ВБС/классический ASP и разработки баз данных, а не ООП.

Я не прошу никого, чтобы написать код, но скорее, чтобы указать мне в правильном направлении. У меня есть этот класс, который я предполагаю, что это то, что я слышал, называют объектом Бога.

Мой код работает, но неуправляемым и будет только хуже.

Этот класс:

  1. Получает информацию об источнике данных, как путь к базе данных Access или имя сервера БД.
  2. Определяет, что двигатель может обработать файл... доступ, СУБД MSSQL, MySQL и...
  3. Выберите провайдера для подключения к СУБД
  4. Задать конкретные параметры дБ, как следует ли использовать одинарные или двойные кавычки вокруг имен объектов и т. д.
  5. Создать строку подключения к БД.

Это нормально, чтобы иметь все эти вещи в один класс?

Я думаю, было бы легче понять, если я разделил его на несколько различных классов:

  1. Класс, чтобы выяснить, какой источник данных будет подключаться к
  2. Класс для установки дБ специальные параметры и сгенерировать строку подключения

Разве это правильно? Если так, вы могли бы порекомендовать может потянуть все эти перечисления и сопровождающие их методы в отдельный статический класс, так что они не должны быть инстанцирован?

using System;
using System.Diagnostics;
using System.IO;

namespace RickRoll
{
public class Db
{
    public string DataSource { get; }
    public string Database { get; }
    public string Username { get; }
    public string Password { get; }
    public string ConnectionString { get; private set; }
    public string ExtendedProperties { get; }

    private WrapColumn wrapcol;
    private WrapObject wrapobj;

    public DataProvider Provider { get; set; }
    public DataFileType FileType { get; private set; }
    public DataEngine Engine { get; private set; }
    public WrapColumn WrapCol { get => wrapcol; private set => wrapcol = value; }
    public WrapObject WrapObj { get => wrapobj; private set => wrapobj = value; }

    // This can't be right, but I needed to do it to get the enun values (temporary fix)
    public Db()
    {
        // only used for public enum access
    }

    // For file databases without password set
    public Db(string filepath)
    {
        FileType = GetDbFileType (filepath);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = filepath;
        Database = Path.GetFileName (filepath);
        Username = null;
        Password = null;
        ConnectionString = GetConnectionString (Provider);          
        GetWrapCol (Engine);
        GetWrapObj (Engine);
    }

    // For file databases with password set
    public Db(string filepath, string password)
    {
        FileType = GetDbFileType (filepath);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = filepath;
        Database = Path.GetFileName (filepath);
        Username = null;
        Password = password;
        ConnectionString = GetConnectionString (Provider);
        GetWrapCol (Engine);
        GetWrapObj (Engine);
    }

    // For all connections that use Windows AUthentication or no password 
    public Db(string path, string server , string database)
    {
        FileType = GetDbFileType (path);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = GetDataSource (Engine , path , server);
        Database = database;
        Username = null;
        Password = null;
        ConnectionString = GetConnectionString (Provider);
        GetWrapCol (Engine);
        GetWrapObj (Engine);
        Debug.WriteLine (ConnectionString);
        Debug.WriteLine (Environment.UserName);
    }

    // For all connection types 
    public Db(string path, string server, string database , string username , string password)
    {
        FileType = GetDbFileType (path);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = GetDataSource (Engine, path, server);
        Database = database;
        Username = username;
        Password = password;
        ConnectionString = GetConnectionString (Provider);
        GetWrapCol (Engine);
        GetWrapObj (Engine);
    }

    // For instantiating from data stored in SQL Server using Windows Authentication or no password
    public Db(int filetype_value, string server , string database)
    {
        FileType = GetDbFileType (filetype_value);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = server;
        Database = database;
        Username = null;
        Password = null;
        ConnectionString = GetConnectionString (Provider);
        GetWrapCol (Engine);
        GetWrapObj (Engine);
        Debug.WriteLine (ConnectionString);
        Debug.WriteLine (Environment.UserName);
    }

    // For instantiating from data stored in SQL Server using SQL Authentication or JET DB password
    public Db(int filetype_value , string server , string database , string username , string password)
    {
        FileType = GetDbFileType (filetype_value);
        Engine = GetDbEngine (FileType);
        Provider = GetDbProvider (FileType);
        DataSource = server;
        Database = database;
        Username = username;
        Password = password;
        ConnectionString = GetConnectionString (Provider);
        GetWrapCol (Engine);
        GetWrapObj (Engine);
    }

    /// <summary>
    /// This changes the database connection string to use a different Provider.
    /// </summary>
    /// <param name="p"></param>
    public void ReSetConnectionString(DataProvider p)
    {
        Provider = p;
        ConnectionString = GetConnectionString ( Provider );
    }

    // Gets file extension from file name or an ext can be passed in place of path
    private DataFileType GetDbFileType(string path)
    {
        DataFileType ft;
        string ext;
        if (path.Contains ("."))
        {  
                 // allows for attached databases 
            string fpath = path.Replace ("|DataDirectory|" , AppDomain.CurrentDomain.BaseDirectory);
            ext = Path.GetExtension (fpath).Replace ("." , "").ToUpper ( );

            if (Enum.TryParse<DataFileType> (ext , out ft) == true) return ft;
        }
        else
        {
            ft = (DataFileType) Enum.Parse (typeof (DataFileType) , path.ToUpper());            /// throw exception if extention is not in DataFileType enum or is null
        }
        return ft;
    }

   // Returns the path to a file for MS Access, etc or a db server name
    private string GetDataSource(DataEngine e, string path, string server)
    {
        if (DataSourceIsFile (e)) return path;
        return server;
    }
   // I think this is unnecessary.  Use this instead:  DataFileType dft = (DataFileType) value;
    private DataFileType GetDbFileType(int value)
    {
        DataFileType ft = (DataFileType) Enum.Parse (typeof (DataFileType) , value.ToString ( ));        // throws and exception if int isn't in the DataFileType enum.
        return ft;
    }

  // This is the db engine
    public enum DataEngine
    {
        None,
        ACCESS = 1,              // 0 = not applicable or not determined
        MSSQL,
        EXCEL,
        ORACLE,
        MYSQL,
        TEXT
    }

    // <summary>
    /// These are the int enum values that get stored in the database for each type of data file
    /// </summary>
    public enum DataFileType
    {
        None,
        MDB = 1,       // 0 = not a configured data file
        ACCDB,
        MDF,           // Primary Data FIle
        NDF,           // File Group (secondary data files)
        XLS,           // Excel 97-2003 worksheet
        XLSX,          // Excel 2007 workbook
        XLSXM,         // Macro enabled workbook
        XLTM,          // Binary worksheet (BIFF12)
        XLW,           // Excel works space, previously known as workbook
        CSV,           // Comma separated values
        TAB,           // Tab separated values
        TSV,           // Tab separated values
        TXT            // Delimited Text file
    }

    /// <summary>
    /// These are the int values that get stored in the database for each db connection
    /// </summary>
    public enum DataProvider
    {
        None,
        Microsoft_ACE_OLEDB_12_0 = 1,          // Microsoft.ACE.OLEDB.12.0 - MS OLEDB DataProvider for MDB or ACCDB or EXCEL
        Microsoft_ACE_OLEDB,               // Microsoft.ACE.OLEDB VersionIndependentProgID
        Microsoft_Jet_OLEDB_4_0,           // MS Access - Does not work with ACCDB or any SQL Server version
        Microsoft_Jet_OLEDB,               // Version Independent ProgID
        SQLNCLI11,                         // SQL Server Native Client for OleDb
        SQLNCLI,                           // Version Independent ProgID
        SQLOLEDB_1,                        // SQL Server OleDb - Does not work with SQL Server Express
        SQLOLEDB,                          // VersionIndependentProgID
        SQL__Server__Native__Client__11_0, // SQL Server Native Client using ODbC
        SQL__Server__Native__Client,       // Version Independent ProgID
        MSDASQL_1,                         // Microsoft OleDb Data Access Components using ODbC
        MSDASQL                            // Version Independent ProgID
    }

  // This can be simplified
    private DataEngine GetDbEngine(int enumvalue)
    {
        DataEngine result = (DataEngine) Enum.Parse (typeof (DataEngine) , enumvalue.ToString ( ));        // throws and exception if int isn't in the DataEngine enum.
        return result;
    }
   // Gets the Database Engine from type of file
    private DataEngine GetDbEngine(DataFileType ft)
    {
        switch (ft)
        {
            case DataFileType.MDB:
                return DataEngine.ACCESS;
            case DataFileType.ACCDB:
                return DataEngine.ACCESS;
            case DataFileType.MDF:
                return DataEngine.MSSQL;
            case DataFileType.NDF:
                return DataEngine.MSSQL;
            case DataFileType.XLS:
                return DataEngine.EXCEL;
            case DataFileType.XLSX:
                return DataEngine.EXCEL;
            case DataFileType.CSV:
                return DataEngine.TEXT;
            case DataFileType.TAB:
                return DataEngine.TEXT;
            case DataFileType.TSV:
                return DataEngine.TEXT;
            case DataFileType.TXT:
                return DataEngine.TEXT;
            default:
                throw new ArgumentException ($"* * * DataFileType is not a supported data file format.  Database DataEngine could not be determined.");
        }
    }


  // Returns the database provider to use for each supported file type
    private DataProvider GetDbProvider(DataFileType ft)
    {
        switch (ft)
        {
            case DataFileType.MDB:
            case DataFileType.ACCDB:
                return DataProvider.Microsoft_ACE_OLEDB_12_0;
            case DataFileType.MDF:
                return DataProvider.SQLNCLI11;                 // SQLOLEDB_1 and SQLOLEDB did not work with SQL Server Express
            case DataFileType.NDF:
                return DataProvider.SQLNCLI11;
            case DataFileType.XLS:
            case DataFileType.XLSX:
            case DataFileType.CSV:
            case DataFileType.TAB:
            case DataFileType.TSV:
            case DataFileType.TXT:
                return DataProvider.Microsoft_ACE_OLEDB_12_0;
            default:
                throw new ArgumentException ($"* * * DataFileType is not a supported data file format.  Database DataProvider could not be determined.");
        }
    }

    // Sets the character used to 'wrap' column names in query strings in case they have spaces or are reserved words
    private void GetWrapCol(DataEngine e)
    {
        switch (e)
        {
            case DataEngine.ACCESS:
                WrapCol = new WrapColumn  { left = "`" , middle = "`,`" , right = "`" };
                break;
            case DataEngine.MSSQL:
                WrapCol = new WrapColumn  { left = "[" , middle = "],[" , right = "]" };
                break;
            case DataEngine.EXCEL:
                WrapCol = new WrapColumn  { left = "`" , middle = "`,`" , right = "`" };
                break;
            case DataEngine.ORACLE:
                WrapCol = new WrapColumn  { left = @"""" , middle = @""",""" , right = @"""" };
                break;
            case DataEngine.MYSQL:
                WrapCol = new WrapColumn  { left = "`" , middle = "`,`" , right = "`" };        // might be brackets for sysnames and ` for columns
                break;
            case DataEngine.TEXT:
                WrapCol = new WrapColumn  { left = @"""" , middle = @""",""" , right = @"""" };        // not sure how to handle this yet
                break;
            default:
                throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine a COLUMN escape character.");
        }
    }

        // Sets the character used to 'wrap' object names (table names, schema names, etc.) in query strings in case they have spaces or are reserved words
    private void GetWrapObj(DataEngine e)
    {
        switch (e)
        {
            case DataEngine.ACCESS:
                WrapObj = new WrapObject { left = "`" , middle = "`,`" , right = "`" };
                break;
            case DataEngine.MSSQL:
                WrapObj = new WrapObject { left = "[" , middle = "],[" , right = "]" };
                break;
            case DataEngine.EXCEL:
                WrapObj = new WrapObject { left = "`" , middle = "`,`" , right = "`" };
                break;
            case DataEngine.ORACLE:
                WrapObj = new WrapObject { left = @"""" , middle = @""",""" , right = @"""" };
                break;
            case DataEngine.MYSQL:
                WrapObj = new WrapObject { left = "[" , middle = "],[" , right = "]" };        // might be brackets for sysnames and ` for columns
                break;
            case DataEngine.TEXT:
                WrapObj = new WrapObject { left = @"""" , middle = @""",""" , right = @"""" };        // not sure how to handle this yet
                break;
            default:
                throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine a OBJECT escape character.");
        }
    }

    // Returns false for server bases DBMS and true for file based Db's like Access
    private bool DataSourceIsFile(DataEngine e)
    {
        switch (e)
        {
            case DataEngine.ACCESS:
                return true;
            case DataEngine.MSSQL:
                return false;
            case DataEngine.EXCEL:
                return true;
            case DataEngine.ORACLE:
                return false;
            case DataEngine.MYSQL:
                return false;
            case DataEngine.TEXT:
                return true;
            default:
                throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine if this is a file or server DataSource.");
        }
    }

    // Connection strings for any of the supported Providers

    //https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax?view=netframework-4.7.1
    private string GetConnectionString(DataProvider Provider)
    {
        string ProgId = Provider.ToString ( ).Replace ("_" , ".");
        string result = "";
        switch (Provider)
        {
            case DataProvider.Microsoft_ACE_OLEDB_12_0:                                                           // Microsoft MS OLEDB for MDB or ACCDB or EXCEL
            case DataProvider.Microsoft_ACE_OLEDB:                                                                // VersionIndependentProgID
                return $@"Provider={ProgId};Data Source={DataSource};Persist Security Info=False;Jet OLEDB:Database Password={Password.EmptyIfNull ( )};";

            case DataProvider.Microsoft_Jet_OLEDB_4_0:                                                            // MS Access Jet OLEDB - Does not work with ACCDB or any SQL Server version
            case DataProvider.Microsoft_Jet_OLEDB:                                                             // VersionIndependentProgID
                // Jet db with user-lvel security requires a Workgroup information file designation:
                // Jet OLEDB:System Database=|DataDirectory|\System.mdw;"   <--- that could be stored in the Server field
                // Jet user-level security uses the User ID and Password setting.  A new constructer that includes the Username field will need to be added to support this 
                if (Engine == DataEngine.ACCESS && Password.NullIfEmpty() == null)
                    return $@"Provider={ProgId};Data Source={DataSource};User ID=Admin;Password=;";
                else
                    return $@"Provider={ProgId};Data Source={DataSource};Persist Security Info=False;Jet OLEDB:Database Password={Password.EmptyIfNull ( )};";


            case DataProvider.SQLNCLI11:                                                                          // SQL Server OleDb Native Client
            case DataProvider.SQLNCLI:                                                                            // VersionIndependentProgID
                result = $@"Provider={ProgId};Server={DataSource};Database={Database};";
                if (Password != null) result += $"Uid={Username.EmptyIfNull ( )};Pwd={Password.EmptyIfNull ( )};";
                if (Password == null) result += "Integrated Security=SSPI;";        
                return result;

            case DataProvider.SQLOLEDB_1:                                                                         // Microsoft OLE DB DataProvider for SQL Server (I've seen this work for ACCESS also) - DID NOT work for SQL Server 2016 Express, but DID work for SQL Server 2016 Developer Edition
            case DataProvider.SQLOLEDB:                                                                           // VersionIndependentProgID
                result = $@"Provider={ProgId};Data Source={DataSource};Initial Catalog={Database};";
                if (Password != null) result += $"User Id={Username.EmptyIfNull()};Password={Password.EmptyIfNull ( )};";
                if (Password == null) result += "Integrated Security=SSPI;";                                                                                            
                return result;

            case DataProvider.MSDASQL_1:                                                                          // Microsoft Data Access OleDb using ODbC
            case DataProvider.MSDASQL:                                                                            // VersionIndependentProgID
                if (Engine == DataEngine.ACCESS)
                    return $@"Driver={{Microsoft Access Driver (*.mdb, *.accdb)}};DbQ={DataSource}";
                if (Engine == DataEngine.EXCEL)
                    return $@"Driver={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DbQ={DataSource}";
                if (Engine == DataEngine.MSSQL)
                    if (string.IsNullOrEmpty (Username))
                        return $@"[DataProvider = MSDASQL;] DRIVER={{SQL Server}}; SERVER={DataSource}; DATABASE={Database};UID=;Integrated Security=SSPI";
                    else
                        return $@"[DataProvider = MSDASQL;] DRIVER={{SQL Server}}; SERVER={DataSource}; DATABASE={Database};UID={Username};PWD={Password}";
                else
                    throw new ArgumentException ($" * * * The MSDASQL Provider has only been set up for MS Access or Excel or MSSQL connections.  Could not create Connection String");


            case DataProvider.SQL__Server__Native__Client__11_0:                                        // SQl Server OleDb using ODbC
                if (Engine == DataEngine.MSSQL && (Username.NullIfEmpty ( ) != null))
                    return $@"Driver={{{ProgId}}};Server={DataSource};Database={Database};Uid={Username};Pwd={Password};";
                else
                    return $@"Driver={{{ProgId}}};Server={DataSource};Database={Database};Trusted_Connection=yes;";

            default:
                throw new ArgumentException ($" * * * DataProvider is not valid.  Could not create Connection String");
        }
    }

    public string Columns(string value)
    {
        string[ ] c = value.Split (',');                                                        // if col names have "," in them, maybe replace it with another char and modify this method to replace it back.
        return $"{WrapCol.left}{string.Join(WrapCol.middle , c)}{WrapCol.right}";
    }



}


public static class StringExt
{
    public static string NullIfEmpty(this string value)
    {
        return string.IsNullOrEmpty (value) ? null : value;
    }

    public static string EmptyIfNull(this string value)
    {
        if (string.IsNullOrEmpty (value))
            return string.Empty;
        else
            return value;
    }

}
}


229
2
задан 14 февраля 2018 в 03:02 Источник Поделиться
Комментарии
2 ответа

Быстрая обратная связь:

Я уточню потом на важных частях.


  • Образец кода, который вы выложили-это длинное и запутанное. Это не оскорбление для того, кто это написал, но это первый признак того, что вы собираетесь нужно уходить больше.

  • Если вы быстро просматривать написал код, вы можете быстро увидеть, появится скопировать шаблон и вставить. Конструкторы, повторный switch Ведомости, ... все это признаки того, что ваш код не достаточно реализации повторно используемых моделей.

  • Все в Всех, вы должны соблюдать твердые больше, чем вы в настоящее время.

  • Вместо того, чтобы использовать перечислимый для включения каждого типа доступа к данным (что означает, что вы собираетесь иметь корпус переключателя где-то дифференцировать нужен код потока), необходимо определение классов , которые делают работу. Это ООП способ делать вещи. Вместо того чтобы развивать функции, которые выполняют все необходимые операции (на перечисление), нужно развивать объекты, которые делают конкретную работу вы хотите.


Основы ООП:

Это может помочь объяснить, цель ООП, как и вы должны его реализовать. Давайте начнем с упрощенный пример:


Человек должен быть создан. У человека есть голова, туловище и ноги. Любая комбинация из головы, туловища и ног является допустимым человека.


  • Параметры головки:


    • Человеческая голова

    • Горилла руководителя

    • Бионический голову


  • Параметры туловища:


    • Торс человека

    • Горилла туловища

    • Киборг туловища


  • Параметры ноги:


    • Деревянные ноги

    • Человеческие ноги

    • Гепард ноги

    • Киборг ноги



Разработчик написал код, вероятно, уже думал об использовании switch случаях различие между этими различными типами головок/туловище/ноги.

Правильный ООП подход будет что-то вроде этого:

public class Human
{
public Head Head { get; set; }
public Torso Torso { get; set; }
public Legs Legs { get; set; }

public Human(Head head, Torso torso, Legs legs)
{
this.Head = head;
this.Torso = torso;
this.Legs = legs;
}

public string CommunicateMessage()
{
return this.Head.SaySomething();
}
}

Глава параметры будут определены, вытекающие из общих Head определение класса:

public enum Material { Flesh, Metal }

public abstract class Head
{
public Material Material { get; }
public bool IsFaceHairy { get; }

public abstract string SaySomething();
}

И тогда вы реализуете функции в производных классах:

public class HumanHead : Head
{
public HumanHead()
{
this.Material = Material.Flesh;
this.IsFaceHairy = false;
}

public override string SaySomething()
{
return "Hello!";
}
}

public class GorilllaHead : Head
{
public GorilllaHead()
{
this.Material = Material.Flesh;
this.IsFaceHairy = true;
}

public override string SaySomething()
{
return "OO OO AH AH AH!";
}
}

public class BionicHead : Head
{
public BionicHead()
{
this.Material = Material.Metal;
this.IsFaceHairy = false;
}

public override string SaySomething()
{
return "YOU WILL BE UPGRADED!";
}
}

То же самое верно для Torso и Legs. Я опустил этот код для краткости.

Это позволит вам создать человека, однако, вы хотите, сборкой их как вам нравится:

Human tom = new Human(new GorillaHead(), new HumanTorso(), new HumanLegs());
Human george = new Human(new HumanHead(), new GorillaTorso, new CheetahLegs());
Human cyberman = new Human(new BionicHead(), new CyborgTorso(), new CyborgLegs());

Важная часть, чтобы забрать из этого примера заключается в том, что каждый Head умеет общаться таким же образом. После сборки человека, и последующий код не нужно заботиться больше что конкретное голову этого человека:

string message1 = tom.CommunicateMessage();
string message2 = george.CommunicateMessage();
string message3 = cyberman.CommunicateMessage();

Не нужно перечислений, случаи переключения, еще IFS, или что-нибудь еще. Это и есть цель объектно-ориентированное программирование. Путем создания правильного определения классов, можно упростить будущем код, который будет использовать эти классы.


Несколько более расширенные комментарии по поводу подхода ООП я использую:


  • Я объявил Head класс как abstract так что невозможно создать new Head(). Абстрактные классы нельзя инстанцировать, только их производные классы могут быть созданы (если они не абстрактные сами).

  • Я выбрал для установки свойств в конструкторе производного класса. Есть и другие способы сделать это, я просто выбрала тот, который ближе всего к тому, что вы уже знаете (так как вы полагались на конструкторы в интернет выложили код)

  • Вы можете использовать интерфейсы, а не абстрактные классы. Опять же, я просто взял в пример легче, и я отказавшись от обсуждения использования интерфейсов.

  • Мой пример упрощен для ясности. Есть много, много разных подходов ООП. Вы должны погрузиться в это глубже, если вы хотите принять обоснованное решение о том, как лучше подойти к этому. Я только пытаюсь дать тебе первый пример того, почему ООП является выгодным для вашей ситуации.



Разделение обязанностей


Примечание: Я oversimplified ваш пример только различают нескольких типов источников данных, для краткости.

В ы в твердой является один принцип ответственности. Более подробная информация здесь, суть ее заключается в том, что ни один класс понятие никогда не должен содержать более одной обязанности.

Из вашего вопроса:


Этот класс:


  1. Получает информацию об источнике данных, как путь к базе данных Access или имя сервера БД.

  2. Определяет, что двигатель может обработать файл... доступ, СУБД MSSQL, MySQL и...

  3. Выберите провайдера для подключения к СУБД

  4. Задать конкретные параметры дБ, как следует ли использовать одинарные или двойные кавычки вокруг имен объектов и т. д.

  5. Создать строку подключения к БД.



Это нормально, чтобы иметь все эти вещи в один класс?

Это не хорошо, потому что это нарушает единый принцип ответственности. Как я понимаю вашу ситуацию, есть несколько различных функций:


  • А - выбор типа источника данных, на который ссылается

  • Б - извлечение данных из источника

  • С - предоставление полученных данных с внешним абонентом

Обратите внимание на письма, я буду использовать их по примеру.

Существование этих 3 ответственность означает, что вам понадобится минимум 3 занятия, чтобы не нарушать ПСП. Спойлер, вам потребуется более чем 3 занятия, но мы вернемся к этому через минуту.


A относительно прост. Он принимает некоторые параметры (параметры, которые вы сейчас проходя в различные конструкторы), и выяснилось, что поставщик данных будет использоваться. В идеале, возвращаемое значение этого метода/класса должны иметь тип B.

Кроме того, он также может вернуть значение типа Cпри условии, что это C объект будет содержать правильный B объект, как одно из его свойств.

Основная цель-обеспечить один класс, который принимает параметры и оценивает их соответственно. После этого оценка была закончена, будущий кодекс не содержит вилки, такие как switch случаях или if блоки предназначены для разграничения между различными типами поставщиков.


C это так же просто. Он предоставляет методы для внешних абонентов, и будет проводить операции на внутреннем B собственность для извлечения данных, что внешний абонент просит.

Основная цель-обеспечить единый класс, который может работать с любым B. Это похоже на Human класс из примера. Каждый Human можно эксплуатировать (внешних абонентов) таким же образом, независимо от того, какая голова/торс/ноги этого человека.
Аналогично, каждый C должен уметь управляться таким же образом, внешний абонент, независимо от того, базовым источником данных является SQL БД, файла Excel или открыть файл.


B является более сложным. Помню Head пример из ООП основы? B по сути будет то же самое: базовый класс, который предназначен для наследования.
В интересах, используя понятные имена, я буду переименовывать B для более понятной имя класса: DataSource (не путать с существующим .Сеть класса с одноименным названием!)

Точно так же, как мы вывели Head для каждого типа головы, которые существовали (HumanHead,GorillaHead,BionicHead), мы будем получать DataSource для каждого типа источника, который существует: ExcelDataSource, SQLDataSource, AccessDataSource, ...

Это где вещи становятся волосатыми. Поскольку ваш код дифференциации между различными типами файла Excel, вы могли бы закончить с многоуровневым наследованием. Что-то вроде этого (отступ = наследование):


  • DataSource


    • ExcelDataSource


      • XlsDataSource

      • XlsxDataSource

      • XlsxmDataSource


    • AccessDataSource

    • SQLDataSource


Также есть несколько других соображений, таких, как например, ExcelDataSource и AccessDataSource происходит от общих FileDataSource; в то же время позволяя SqlDataSource и MySqlDataSource вытекают из общей NetworkDataSource. Это все возможные предложения. Вам нужно выбрать один, который соответствует вашим потребностям, и этот ответ не может вникать в эти мелкие детали.

Основные преимущества такого подхода:


  • Вы можете избежать копирования/вставки кода путем реализации логики на правильном уровне дерева наследования:


    • Если все источники данных Excel (файл XLS, в формате XLSX или xlsm, ...) имеют общую логику (например, проверить, если файл существует), вам нужно только реализовать эту логику в общий класс (ExcelDataSource.FileExists())

    • Если определенный файл Excel (напр. XLSX) имеет уникальный часть логики, не применяются для любого другого типа файла Excel, то вы можете реализовать эту логику в конкретном классе (XlsxExcelDataSource)

    • Если вы реализуете очень похожий кусок логики в несколько классов, есть хороший шанс, что вы должны поставить эту логику в базовый класс, что эти классы наследуют от. Это позволяет избежать дублирования кода.


  • Ваш C объект будет иметь DataSource объект. Важно понимать, что C объект не позволено знать/уход за которой определенного производного класса DataSource используется. Это позволяет C быть B-агностик (= заключения, основных принципа ООП).

  • Наследование может обеспечить дополнительную безопасность типов, например, вы можете ограничить определенный способ только на прием, например ExcelDataSource предметы вместо, например неостроумно также принятие SqlDataSource объекты. При использовании перечислений, это было невозможно без того, чтобы вручную проверить перечисление и бросать исключение на этапе выполнения (а не компиляции ошибка, что гораздо лучше для разработчика).

  • Если вдруг придется добавить новые источники данных (или удалить существующие), стоимость реализации этого изменения сводится к минимуму. Вам просто нужно создать новый xxxxxDataSource класс (независимо от существующих классов), и необходимость обновления логики A с учетом этого нового класса существование в своей оценке логики.


    • В старой системе, вам бы пришлось создать новое значение типа enum, а затем обновить все switch случаи, которые оценивают данное перечисление.

    • Если вы забыли обновить один или два switch случаях, они будут использовать их default случае (без ошибки), и вы будете в конечном итоге с неожиданным поведением, что происходит, чтобы быть раздражающим для отладки (вы бы наступать через каждые switch чтобы посмотреть, что происходит).




Заключение
Я не могу дать вам окончательного ответа, потому что это сильно зависит от вашей ситуации. Но намерение и цель должна быть ясна: вместо того, чтобы использовать перечисления, вы должны использовать определения классов и наследования. Это позволяет максимизировать повторное использование кода, не копируйте вопросы, а также избежать ненужных switch или if блоки.


Как приступить к реализации этого?

Это простейший пример, чтобы показать вам как вы можете начать разделение существующего класса. Если ваш старый класс выглядит так:

public class OldExampleDataSource
{
private ConnectionType MyConnectionType { get; set; }

public OldExampleDataSource(string a)
{
//SQL constructor logic
}

public OldExampleDataSource(bool b)
{
//Excel constructor logic
}

public void DoSomething()
{
switch (MyConnectionType)
{
case ConnectionType.SQL:
//SQL oepration logic
case ConnectionType.Excel:
//Excel operation logic
default:
break;
}
}
}

Вам необходимо группировать логику в отдельные классы, в зависимости от типа подключения:

public class DataSource
{
public DataSource()
{
//Default constructor logic
}

public virtual void DoSomething()
{
//Default operation logic
}
}

public class SqlDataSource : DataSource
{
public SqlDataSource()
{
//SQL constructor logic
}

public void DoSomething()
{
//SQL operation logic
}
}

public class ExcelDataSource : DataSource
{
public ExcelDataSource()
{
//Excel constructor logic
}

public void DoSomething()
{
//Excel operation logic
}
}

Здесь, вы видите основное преимущество появляются: даже если вы сохранили все конструктора и логика работы у вас отпала необходимость в enum и те последующие switch случаях.

5
ответ дан 15 февраля 2018 в 09:02 Источник Поделиться

Слишком много для комментария.

Это кажется очень широким набором источников данных, чтобы думать, вы можете иметь общий набор команд / запросов.

Если вы идете с .Данных net предоставляет вам общий набор методов и свойств через dbcommand в классе. Например объект DataReader.

Вы можете найти 3-го лица для многих источников. Во многих источниках (я думаю, что Excel входит) поддержка данных oledb или ODBC. Я не использовал один, но я видел .Чистая поставщиками данных для CSV.

1
ответ дан 14 февраля 2018 в 04:02 Источник Поделиться