2009-12-25 58 views
2

除了使用单一职责原则,设计类应用的一个是写作,什么人应该记住的时候,要保持代码的可维护性,可重用性和坚持OOP原则是什么?设计应用类

我发现它很难设计的应用程序,我试图写的类,因为什么时候一个决定什么(功能)变为哪一类,是否真的应该在派生类中还是应该有这个类的抽象类或接口?

我知道这可能是有许多答案的话题,但没有任何人有任何好的指引(最好是简单的),以设计类和类层次结构是简单的维护和创建大型应用程序时,不要让一个烂摊子?

编辑:

当有具有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 
    } 
+1

一个简单的规则 - 不要设计类层次结构。尽管你可能已经被告知了一切,但这个世界并不是基于这样的事情。 – 2009-12-25 23:08:38

+0

好吧Neil。你能详细说明你的意思吗? – 2009-12-25 23:12:53

+1

嗯,要真正解释他将不得不写一篇不错的文章。 – ChaosPandion 2009-12-25 23:13:34

回答

0

这是那些东西,他们很少在学校教一个,甚至当他们这样做的一般一个人为的例子,仍然没有给你一个好的想法如何做到这一点。

底线是,有真正地不是一个好具体的,科学的方式做到这一点,毕竟计算机编程仍然是非常艺术。 :)

我喜欢做的是:

  1. 写什么样的应用程序是假设做子弹点形式,高层次的东西简要概述
  2. 鸿沟要点成“要求“,”可选“,”很好“。其中,“需要”是的东西,一定是那里甚至“演示”和可选的是,基本上使得它在顾客眼前一个完整的应用程序的东西
  3. 如果需要做我的图纸,图表,UML,规格等在这一点上(取决于需要什么)
  4. 开始写代码。我喜欢草拟我在代码中所做的事情,并开始弄清楚事情是如何布置的,以及需要什么功能
  5. 工作到最初的原型(“需要”的东西)一旦我到达这一点,我冻结并开始看看设计在哪里,以及反馈是什么
  6. 开始优先重构重构和重新设计并修复原型中的问题。
  7. 重构!重新设计和重新实现

冲洗,起泡和重复。

对于一些事情,它明显的是什么进入哪些对象,哪些类是需要的,哪些设计运行良好。对于其他事情,需要花费一两个时间才能找出错误。

我学到了一些新的项目,并且找到了应用概念的新方法,我认为我理解得很好。它是一个不断的学习体验,总有一些东西需要学习。

2

我希望我早期学到的一件事是设计代码,通过非常简单的接口而不是大量的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

6

听我张贴一些句子,我没有从我最喜欢的书“为企业架构的Microsoft®.NET解决方案”,我强烈建议阅读这本书,即使你不是一个软件建筑师。

It Depends 它总是取决于。作为一名建筑师,你从不确定任何事情。总有可能你错过了一些东西。但是,角色需要作出决定,所以您必须能够评估所有选项并做出明智的决策,并在需要做出决定时及时做到这一点。为了给自己买点时间并在后台激活你的心理过程,首先要说“这取决于”,然后解释为什么和答案依赖于什么。如果您不确定某个点依赖于什么,则默认答案是“这取决于上下文”。

要求主全部 建筑师只是软件项目中演员自然链中的一个环节。顾客说他想要什么。如果客户不知道他想要什么,那么有人会在那里提示他提供具体信息。分析师正式确定客户想要的东西。项目经理为正式定义的项目奠定基础。建筑师获得了一系列要求并将其整理出来。开发人员遵循这个架构师。数据库管理员尽力使数据库有效地支持应用程序。请注意,客户领导连锁店,客户需要的是法律。客户需要的是以需求为名。当然,只有少数客户知道他们想要什么。因此需求改变。

编程接口 即使您不想使用已实现的代码,您应该尽可能地利用接口。重复我们的话:“没有接口,没有实现是可能的。”环顾四周,总有一个可以提取的界面。

保持简单但不简单 你知道KISS(保持简单,愚蠢),对吗?这只是我们的定制版本。简单而简洁通常相当于做得很好,做得很好。旨在简化,但给自己一个范围的低端的边界。如果你低于这个下限,你的解决方案将变得简单化。这不是一件好事。

继承了多态,不能再用 面向对象编程(OOP)告诉我们,我们应该一次写一个类,永远重复使用和随意扩展。这可以归功于继承。这是否自然延伸到类重用?重用是一个比你想象的更微妙的概念。多态性是OOP利用的关键方面。多态意味着可以交替使用两个继承类。正如其他人所说,“重用是一个很好的副作用。”但重用不应该是你的目标,或者换句话说,不要通过继承重用类来重用类。最好是编写一个更精确地适合需求的新类,而不是试图继承一个不适合工作的现有类。

不是DAL?不要触摸SQL然后 重复我们:“分离顾虑,分离顾虑。”将数据访问代码和详细信息(例如连接字符串,命令和表名)推到角落。迟早,你需要照顾他们,但考虑业务和表现逻辑与持久性分开。如果可能的话,将持久性委托给对象/关系映射器(O/RM)工具等临时工具。

可维护性第一 如果您只能为您的软件选择一个属性,它会是什么?可扩展性?安全?性能?可测试性?可用性?对我们来说,这将不是上述情况。对我们来说,首先是可维护性。通过可维护性,您可以随时获得其他任何内容。

所有的用户输入是邪恶的 你应该已经听到了。如果有一种方式让用户做错了,他们会发现它。哦,这听起来像墨菲定律。是的,你也应该听过这个。

死后优化 唐纳德克努特说过早优化是所有软件邪恶的根源。我们走得更远。不要优化系统。相反,设计它是为了随时改进和扩展。但只有在系统被解雇时才会关注纯粹的优化。

安全性和可测试性设计 如果您认真对待系统属性,请从头开始为其设计。安全性和可测试性对此规则也不例外,并且国际标准化组织(ISO)标准有专门的说法。

我希望你能帮到你。

+0

我碰巧在我的书架上有这本书。尽管如此,我还没有完全阅读。也许该是我该再次捡起来的时候了! – 2009-12-25 23:24:33

+0

许多好点的+1!不过,我会质疑分析师/建筑师/开发者部门的需求。它意味着瀑布和BDUF。 – TrueWill 2009-12-26 00:07:51

+0

+ 1不错的概括性,但与现实世界的软件开发相比“理想化”;但是,我宁愿向那些“真理”祈祷,而不是敏捷/ Scrum或TDD :)“与我们一起重复:”没有实现...没有接口。“对不起,我们不是在那个崇拜中,我认为一个“明显的遗漏”:建筑师应该深刻理解数据的结构,动态;它可能会改变的方式,随着时间的推移而演变:想象一下,房子建筑师不知道地质,地形,土壤负载等因素,轴承强度,风,降雨量,附近建筑物投下的阴影等。:) – BillW 2009-12-26 01:39:25

3

好,除了保持代码的可维护性,可重复使用,并秉承OOP原则... :)

我会小心,以避免分析瘫痪在这里:如果你不做出正确的选择例如,类,派生类或接口之间,这就是重构的目的。事实上,我认为这就是SRP这样的原则 - 不要轻易事先设计所有东西,而是为了方便变更随着您的需求增长和变化的形状,因为您的可以'预先预测一切。

设计代码之间存在紧张关系,可重复使用,只是创建了一些可以使用的东西。尽可能地设计重用,但不要让它妨碍只执行正确的要求。

我听说过的一个提示(可能是Spolsky提供的)是:当你需要在一个地方执行操作时,编写代码。当你需要在另一个地方执行它时,再次编写代码。当您想要在第三个位置执行相同的操作时,现在是时候考虑重构。

不要试图设计一个庞大的,包罗万象的系统,希望避免变化 - 设计代码有弹性要改变。

+0

+1非常雄辩地说,imho – BillW 2009-12-26 01:47:47

1

在Stackoverflow上有一个good question讨论C#反模式,以及如何避免它们。

你应该快速阅读;它暴露了很多你可能想要做/不知道的东西是错误的,并且对如何以更优雅的方式完成相同的功能提出了建议。