除了使用单一责任原则之外,在为应用程序设计类时,我们应该记住,应该记住什么,保持代码可维护,可重用并遵守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
}
答案 0 :(得分:6)
听到我发布了一些我从我最喜欢的书“为企业构建Microsoft®.NET解决方案”中获得的一句话,即使您不是软件架构师,我强烈建议您阅读本书。
取决于 它总是取决于。作为一名建筑师,你永远不会确定任何事情。总有可能你错过了什么。但是,角色需要做出决策,因此您必须能够评估所有选项并做出明智的决定,并在需要做出决定时迅速做出决定。为了给自己买一些时间并在后台激活你的心理过程,先说“这取决于”,然后解释为什么以及答案取决于什么。如果您不确定某个点取决于什么,默认答案是“这取决于上下文。”
要求是主啊 架构师只是软件项目中自然链中的一个链接。顾客说出他想要的东西。如果顾客不知道他想要什么,有人会在那里提示他具体细节。分析师正式确定了客户的需求。项目经理为正式定义的项目准备基础。架构师获得了一系列需求并对其进行排序。开发人员遵循架构师。数据库管理员尽最大努力使数据库有效地支持应用程序。请注意,客户领导链条,客户想要的是法律。客户想要的是以要求为名。当然,只有少数客户知道他们想要什么。因此需求发生了变化。
编程到界面 即使您使用已实现的代码谋生,也应尽可能利用接口。与我们重复:“没有接口就无法实现。”环顾四周,总有一个可以提取的界面。
保持简单但不简单 你知道KISS(保持简单,愚蠢),对吗?这只是我们的定制版本。简单而简洁通常相当于伟大而且做得好。瞄准简单,但给自己一个低端范围的边界。如果你低于那个较低的边界,你的解决方案将变得简单化。这不是一件好事。
继承是关于多态,不重用 面向对象编程(OOP)告诉我们,我们应该编写一次类并永久地重用它并随意扩展它。这可以归功于继承。这自然会扩展到类重用吗?重用是一个比你最初想象的更微妙的概念。多态性是OOP利用的关键方面。多态性意味着您可以交替使用两个继承的类。正如其他人所说,“重用是一个很好的副作用。”但重用不应该是你的目标,或换句话说,不要通过继承重用一个类来重用该类。编写一个更符合需求的新类比尝试继承不是为该作业设计的现有类更好。
不是DAL?不要触摸SQL然后 与我们重复:“关注点分离。关注点分离。”将数据访问代码和详细信息(例如连接字符串,命令和表名称)推送到角落。迟早,您需要处理它们,但将业务和表示逻辑与持久性分开考虑。如果可能,将持久性委托给对象/关系映射器(O / RM)工具等临时工具。
可维护性首先 如果您只为您的软件选择一个属性,它会是什么?可扩展性?安全?性能?可测试性?可用性?对我们来说,它不会是上述情况。对我们来说,首先是可维护性。通过可维护性,您可以随时实现其他任何目标。
所有用户输入都是邪恶的 你应该已经听过了。如果用户有办法做错事,他们会找到它。哦,这听起来像墨菲定律。是的,你也应该已经听过这个了。
事后优化 唐纳德克努特说过早优化是所有软件邪恶的根源。我们走得更远。不要优化系统。相反,设计它可以随时进行改进和扩展。但只有在系统被解雇时才专注于纯粹的优化。
安全性和可测试性是设计性的 如果您认真对待系统属性,请从头开始设计。安全性和可测试性也不例外,并且有一个国际标准化组织(ISO)标准,具体说明了这一点。
我希望你能帮助你。
答案 1 :(得分:3)
好吧,除了保持代码可维护,可重用和遵守OOP原则......:)
我要小心避免这里的分析瘫痪:例如,如果你没有在类,派生类或接口之间做出正确的选择,那就是重构的目的。事实上,我认为这就是SRP这一原则的全部要点 - 不是为了让事先设计好所有事情变得容易,而是为了在你的需求增长和改变形状时轻松改变事物,因为你不能事先预测一切。
在设计可重复使用的代码与创建可用的代码之间存在着紧张关系。设计可以尽可能地重用,但不要让它妨碍仅仅实现在您面前的要求。
我听过的一个提示(可能是Spolsky的提供)是:当您需要在一个地方执行操作时,请编写代码。当您需要在其他地方执行时,请再次编写代码。当您想在第三个位置执行相同的操作时,现在是时候考虑重构了。
不要试图设计一个庞大的,无所不包的系统,以避免变更 - 设计代码弹性改变。
答案 2 :(得分: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
答案 3 :(得分:1)
Stackoverflow上有good question讨论C#反模式,以及如何避免它们。
你应该快速阅读;它暴露了很多你可能想要做的事情/没有意识到是错的,并提出了如何以更优雅的方式完成相同功能的建议。
答案 4 :(得分:0)
这是他们很少在学校教授的东西之一,即使他们做的通常是一个人为的例子,但仍然不能让你知道如何去做。
最重要的是,在所有的计算机编程仍然是一门艺术之后,确实没有一个好的具体,科学的方法来做到这一点。 :)
我喜欢做的是:
冲洗,起泡并重复。
对于某些事情,显而易见的是哪些对象,哪些类需要以及哪些设计运行良好。对于其他事情,需要一两次迭代来解决错误。
我在项目中学到了一些新东西,并且找到了应用我认为理解得很好的概念的新方法。它是一个不断学习的经历,总有一些东西需要学习。