проектирование классов приложений - PullRequest
2 голосов
/ 26 декабря 2009

Помимо использования принципа единой ответственности, при разработке классов для приложения пишется, что следует иметь в виду, чтобы код можно было обслуживать, использовать повторно и придерживаться принципов ООП?

Мне трудно проектировать классы приложений, которые я пытаюсь написать, потому что, когда кто-то решает, что (функциональность) входит в какой класс и должен ли он действительно быть в производном классе или должен быть абстрактный класс или интерфейс для этого класса?

Я знаю, что это, вероятно, тема с множеством ответов, но есть ли у кого-нибудь хорошие рекомендации (желательно простые) по проектированию классов и иерархий классов, которые просты в обслуживании и не создают беспорядка при создании больших приложений?

EDIT:

Когда есть классы, которые имеют 10 методов и более и имеют абстрактный базовый класс и интерфейсы, из которых он получен. Также есть 3 класса Singleton, на которые имеются глобальные ссылки внутри класса, и многое другое. Похоже, нужно немного применить «рефакторинг»?

Извините, если это длинный пример, но вы видите проблему, с которой я сталкиваюсь, и я хочу внести свой вклад в это. Пожалуйста, смотрите на дизайн, а не на технические детали.

Я приведу пример:

Я создал этот класс: (некоторое время назад)

  class ExistingUserLogon : Logon, ILogonUser
    {
        #region Member Variables

        LogonEventArgs _logoneventargs;
        LogonData lgndata;
        Factory f = Factory.FactoryInstance;
        PasswordEncrypt.Collections.AppLoginDataCollection applogindatacollection;
        PasswordEncrypt.Collections.SQlLoginDataCollection sqllogindatacollection;
        bool? compare;
        static ExistingUserLogon existinguserlogon;
        PasswordEncrypt.SQLDatabase.DatabaseLogin dblogin;
        string databasename = string.Empty;

        #endregion

        #region Properties

        public static ExistingUserLogon ExistingUserLogonInstance
        {
            get
            {
                if (existinguserlogon == null)
                    existinguserlogon = new ExistingUserLogon();
                return existinguserlogon;
            }
        }
        public string loginname
        {
            get;
            set;
        }
        #endregion

        #region Contructors
        public ExistingUserLogon(bool? compare, LogonData logondata)
        {
            this.compare = compare;
            this.lgndata = logondata;
            this.applogindatacollection = f.AppLoginDataCollection;
            this.sqllogindatacollection = f.SqlLoginDataCollection;
        }

        public ExistingUserLogon()
        {
            this.applogindatacollection = f.AppLoginDataCollection;
            this.sqllogindatacollection = f.SqlLoginDataCollection;
        }

        #endregion

        #region Delegates

        public delegate void ConnStrCreated( object sender, LogonEventArgs e );

        #endregion

        #region Events

        public event ConnStrCreated ConnectionStringCreated;

        #endregion

        private void OnConnectionStringCreated( object sender, LogonEventArgs e )
        {
            if (ConnectionStringCreated != null)
            {
                ConnectionStringCreated(sender, e);
            }
        }

        public void LoginNewUser()
        {
            dblogin = new PasswordEncrypt.SQLDatabase.DatabaseLogin();
            if (!string.IsNullOrEmpty(loginname))
            {
                string temp = dblogin.GenerateDBUserName(loginname);
                if (temp != "Already Exists")
                {

                    if (compare == true)
                    {
                        IterateCollection(lgndata.HahsedUserName.HashedUserName, new Action<string>(OnDatabaseName));
                        IterateCollection(temp, new Action<bool, string, string>(OnIterateSuccess));
                    }
                }

            }
            else
            {
                LogonEventArgs e = new LogonEventArgs();
                e.Success = false;
                e.ErrorMessage = "Error! No Username";
                OnError(this, e);

            }

        }

        private void OnDatabaseName(string name)
        {
            this.databasename = name;
        }

        private void OnIterateSuccess( bool succeed, string psw, string userid )
        {
            if (succeed)
            {
                // Create connectionstring
                ConnectionStringCreator cnstrCreate = ConnectionStringCreator.ConnectionStringInstance;
                if (databasename != string.Empty)
                {
                    string conn = ConnectionStringCreator.CreateConnString(databasename, userid, psw);
                    bool databaseExists;

                    databaseExists = DataManagementVerification.DoDatabaseExists(conn);

                    if (databaseExists)
                    {
                        FormsTransfer.ConnectionString = conn;

                        conn = string.Empty;
                    }
                    else
                    {
                        LogonEventArgs e = new LogonEventArgs();
                        e.Success = false;
                        e.ErrorMessage = "Database does not Exist!";
                        OnError(this, e);
                    }

                    //OnConnectionStringCreated(this, e);

                   // PasswordEncrypt.LINQtoSQL.PasswordDatabase db = new PasswordEncrypt.LINQtoSQL.PasswordDatabase(conn);

                /*    PasswordEncrypt.LINQtoSQL.EncryptData _encryptdata = new PasswordEncrypt.LINQtoSQL.EncryptData()
                    {
                        EncryptID = 1,
                        IV = "Test",
                        PrivateKey = "Hello",
                        PublicKey = "Tony"
                    };

                    db.EncryptionData.InsertOnSubmit(_encryptdata);

                   // db.SubmitChanges();*/

                    //PasswordEncrypt.LINQtoSQL.Data _data = new PasswordEncrypt.LINQtoSQL.Data()
                    //{
                    //    EncryptionID = 1,
                    //    Username = "Tbone",
                    //    Password = "worstje",
                    //    IDCol = 2
                    //};

                    //db.Data.InsertOnSubmit(_data);

                    //db.SubmitChanges();

                    //IEnumerable<PasswordEncrypt.LINQtoSQL.Data> _ddata = db.Data.Where(data => data.Password == "worstje"); ;
                    //foreach (PasswordEncrypt.LINQtoSQL.Data data in _ddata)
                    //{

                    //    MessageBox.Show("Data Found: " + data.Username + "," + data.Password);
                    //}
                }
                else
                {
                    MessageBox.Show("Found no Database name", "Database name error");
                }
            }
            else
            {
                // Unable to create connectionstring
            }
        }

        private void IterateCollection( string username, Action<bool, string, string> OnSucceed )
        {
            bool succeed = false;
            dblogin = new PasswordEncrypt.SQLDatabase.DatabaseLogin();

            string psw;
            string userid;

            foreach (KeyValuePair<PasswordEncrypt.Collections.GItem<string>, PasswordEncrypt.Collections.GItem<string>> kv in sqllogindatacollection)
            {
                List<char> tempa = new List<char>();
                List<char> tempb = new List<char>();

                tempa = dblogin.GetNumber(username);
                tempb = dblogin.GetNumber(kv.Key.Item);

                if (tempa.Count == tempb.Count)
                {
                    for (int i = 0; i < tempa.Count ; i++)
                    {
                        if (tempa.ElementAt(i).Equals(tempb.ElementAt(i)))
                        {
                            if ( i == (tempa.Count -1) )
                                succeed = true;
                            continue;
                        }
                        else
                        {
                            KeyValuePair<PasswordEncrypt.Collections.GItem<string>, PasswordEncrypt.Collections.GItem<string>> last =  sqllogindatacollection.Last();
                            if (kv.Key.Item.Equals(last.Key.Item))
                            {
                                MessageBox.Show("Failed to match usernames for Database", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                                succeed = false;
                                // Let GUI Know...
                                LogonEventArgs e = new LogonEventArgs();
                                e.Success = succeed;
                                OnError(this, e);
                                break;
                            }
                            else
                            {
                                break;
                            }

                        }

                    }


                   // MessageBox.Show("Found a sql username match");
                }
                // Now go execute method to logon into database...
                if (succeed)
                {
                    psw = kv.Value.Item;
                    userid = kv.Key.Item;
                    OnSucceed(succeed, psw, userid);
                }
                succeed = false;
            }


        }

        private void IterateCollection(string key, Action<string> OnDatabaseName )
        {
            foreach (KeyValuePair<PasswordEncrypt.Collections.GItem<string>, PasswordEncrypt.Collections.GItem<string>> kv in applogindatacollection)
            {
                if (key == kv.Key.Item)
                {
                    MessageBox.Show("Found a match");
                    OnDatabaseName(kv.Value.Item);
                }
                else
                {
                   // MessageBox.Show("No Match");
                }
            }
        }

        #region Public Methods

        public bool? ReadFromRegistry( HashedUsername username, HashedPassword hashedpassword )
        {
            return RegistryEdit.ReadFromRegistry(username, hashedpassword);
        }

        public bool WriteToRegistry( HashedUsername username, HashedPassword hashedpassword )
        {
            return RegistryEdit.WriteToRegistry(username, hashedpassword);
        }

        public override void Login(TextBox username, TextBox password)
        {
            base.Login(username, password);
            Login(username.Text, password.Text);
        }

        protected override void Login(string username, string password)
        {
            base.Login(username, password);
            lgndata = base._logondata;
            compare = base._regRead;

            if (compare == true)
            {
                loginname = username;
                LoginNewUser();
                /// on login succeeded let UI class know
                _logoneventargs = new LogonEventArgs();
                _logoneventargs.Success = true;

                OnExistingUserLoggedIn(this, _logoneventargs);
            }
            else
            {
                _logoneventargs = new LogonEventArgs();
                _logoneventargs.Success = false;
                _logoneventargs.ErrorMessage = "Cannot Login this user, please try again.";
                OnError(this, _logoneventargs);
            }
            /// Get username and password for database... 
            /// to login using correct user data & permissions
            /// Login data for database is generated at runtime
            /// then by checking if database with such a name exists
            /// login...

        }
        #endregion
    }

Ответы [ 5 ]

6 голосов
/ 26 декабря 2009

Слышу, как я публикую часть предложения, которое я взял из моей любимой книги "Архитектура решений Microsoft® .NET для предприятия", и я настоятельно рекомендую прочитать эту книгу, даже если вы не являетесь архитектором программного обеспечения.

Это зависит Это всегда зависит. Как архитектор, вы никогда ни в чем не уверены. Всегда есть вероятность, что вы что-то упустили. Тем не менее, роль требует принятия решений, поэтому вы должны иметь возможность оценить все варианты и принять обоснованное решение, и делать это быстро, когда требуется решение. Чтобы выиграть время и активировать свои умственные процессы в фоновом режиме, сначала скажите «Это зависит», а затем объясните, почему и от чего зависит ответ. Если вы не уверены, от чего зависит точка, ответ по умолчанию будет «Это зависит от контекста».

Требования господствуют над всеми Архитектор - это всего лишь одно звено в естественной цепочке актеров в программном проекте. Клиент говорит, что хочет. Если клиент не знает, что он хочет, кто-то будет там, чтобы подсказать ему конкретику. Аналитик формализует то, что хочет клиент. Менеджер проекта готовит основу для формально определенного проекта. Архитектор получает набор требований и разбирает их. Разработчики следуют за архитектором. Администратор базы данных делает все возможное, чтобы база данных эффективно поддерживала приложение. Обратите внимание, что клиент ведет цепочку, и что клиент хочет, это закон. То, что хочет клиент, идет под названием требований. Конечно, лишь немногие клиенты знают, чего они хотят. Так что требования меняются.

Программа для интерфейса Даже если вы зарабатываете на жизнь реализованным кодом, вы должны использовать интерфейсы везде, где это возможно. Повторите с нами: «Реализация невозможна без интерфейса». Посмотрите вокруг, всегда есть интерфейс, который можно извлечь.

Сохраняйте это простым, но не упрощенным Вы знаете, ПОЦЕЛУЙ (Keep It Simple, глупый), верно? Это просто наша индивидуальная версия. Простой и лаконичный, как правило, эквивалентен великому и хорошо сделано. Стремитесь к простоте, но дайте себе границу для нижнего предела диапазона. Если вы пойдете ниже этой нижней границы, ваше решение станет упрощенным. И это не очень хорошая вещь.

Наследование связано с полиморфизмом, а не с повторным использованием Объектно-ориентированное программирование (ООП) научило нас, что мы должны написать класс один раз, использовать его вечно и расширять его по желанию. И это возможно благодаря наследованию. Естественно ли это распространяется на повторное использование классов? Повторное использование является гораздо более тонкой концепцией, чем вы думаете на первый взгляд. Полиморфизм является ключевым аспектом ООП для использования. Полиморфизм означает, что вы можете использовать два унаследованных класса взаимозаменяемо. Как уже говорили другие, «Повторное использование - это хороший побочный эффект». Но повторное использование не должно быть вашей целью, или, другими словами, не используйте класс через наследование просто для повторного использования класса. Лучше написать новый класс, который более точно соответствует потребностям, чем пытаться наследовать существующий класс, который не предназначен для этой работы.

Не DAL? Не трогайте SQL тогда Повторите с нами: «Разделение интересов. Разделение интересов». Выдвиньте код доступа к данным и данные (например, строки подключения, команды и имена таблиц) в угол. Рано или поздно вам нужно позаботиться о них, но рассматривайте бизнес и логику представления отдельно от постоянства. И, если возможно, делегируйте постоянство специальным инструментам, таким как инструменты Object / Relational Mapper (O / RM).

Первый ремонт Если бы вы могли выбрать только один атрибут для своего программного обеспечения, что бы это было? Масштабируемость? Безопасность? Спектакль? Тестируемость? Юзабилити? Для нас это было бы ничего из вышеперечисленного. Для нас на первом месте стоит ремонтопригодность. Благодаря удобству обслуживания вы можете в любое время достичь чего-либо еще.

Все вводимые пользователем данные зла Выдолжен был услышать это уже. Если у пользователей есть возможность сделать что-то не так, они найдут это. О, это звучит как закон Мерфи. Да, ты уже должен был это услышать.

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

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

Надеюсь, вам это поможет.

3 голосов
/ 26 декабря 2009

Ну, кроме того, что мы поддерживаем код поддерживаемым, многократно используемым и придерживаемся принципов ООП ...:)

Я бы постарался избежать паралича анализа: если вы не сделаете правильный выбор между классом, производным классом или интерфейсом, например, для этого и нужен рефакторинг. На самом деле, я бы сказал, что в этом вся суть принципов, таких как SRP, - не в том, чтобы все было легко спроектировать заранее, а в том, чтобы упростить изменение вещей по мере роста ваших требований и изменения формы, потому что Вы не можете предсказать все заранее.

Существует противоречие между разработкой кода для повторного использования и созданием чего-то, что годно для использования . Разработайте для повторного использования, где вы можете, но не позволяйте этому мешать просто реализации требований, которые прямо перед вами.

Один совет, который я слышал (возможно, от Spolsky): когда вам нужно выполнить операцию в одном месте, напишите код. Когда вам нужно выполнить это в другом месте, напишите код еще раз. Если вы хотите выполнить ту же операцию в третьем месте, теперь пришло время подумать о рефакторинге.

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

2 голосов
/ 26 декабря 2009

Одна вещь, которую я хотел бы узнать на ранних этапах, - это разработать код для взаимодействия через очень простые интерфейсы, а не через массивные API. Я бы порекомендовал вам даже сделать это внутренне. Создайте простые интерфейсы, которые взаимодействуют между вашими модулями. Это делает тестирование намного проще.

private interface IPerformWork
{
    void DoThis(String value);
    void DoThat(String value);  
}

Помните, что чем меньше ваш код, тем лучше.

Кодовый запах

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

https://stackoverflow.com/questions/114342/what-are-code-smells-what-is-the-best-way-to-correct-them

1 голос
/ 26 декабря 2009

Здесь, на Stackoverflow, есть хороший вопрос , в котором обсуждаются антишаблоны C # и способы их предотвращения.

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

0 голосов
/ 26 декабря 2009

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

Суть в том, что действительно нет конкретного, научного способа сделать это, ведь компьютерное программирование все еще остается искусством. :)

Что мне нравится делать:

  1. Напишите краткое описание того, что приложение должно делать, высокоуровневые вещи в форме маркера
  2. Разделите маркеры на «обязательные», «необязательные», «приятные». Там, где «требуется» - это то, что должно присутствовать даже для «демонстрации», а необязательное - это то, что в принципе делает его полноценным приложением в глазах клиентов
  3. Если требуется, делайте мои чертежи, диаграммы, UML, спецификации и т. Д. На данный момент (зависит от того, что требуется)
  4. Начните писать код. Мне нравится чертить то, что я делаю в коде, и начинать выяснять, как все устроено и какие функциональные возможности должны быть там, где
  5. Работая с первоначальным прототипом («требуемый» материал), как только я достигаю этой точки, я замираю и начинаю смотреть, где находится дизайн, и каковы отзывы
  6. Начните устанавливать приоритеты рефакторинга и редизайна с исправлением проблем в прототипе.
  7. Рефакторинг! Редизайн и переопределение

Промыть, вспенить и повторить.

Для некоторых вещей очевидно, что входит в какие объекты, какие классы нужны и какие проекты работают хорошо. Для других вещей требуется итерация или две, чтобы устранить ошибки.

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

...