Besides using the Single Responsibility Principle, when designing classes for an application one is writing, what should one keep in mind, to keep the code maintainable, reusable and adhere to OOP principles?
I'm finding it hard to design the classes of applications I'm trying to write, because when does one decide what (functionality) goes in which class and whether it should really be in a derived class or there should be an abstract class or interface for this class?
I know this is probably a topic with many answers, but does anyone have any good guidelines (preferably simple) to designing classes and class hierarchies that are simple to maintain and don't make a mess when creating big applications?
EDIT:
When there's classes that have 10 methods and over and have an abstract base class & interfaces which it derives from. Also has 3 Singleton classes referenced globally inside the class and much more. Sounds like it needs a bit of 'refactoring' applied?
Sorry if it's a long example, but you see the problem I'm facing and I want some input on it. Please just look at design, not at technicalities.
I give an example:
I created this class: (a while back)
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
}