For one of my Android projects I have created a framework (for lack of a better word) of interacting with my SQLiteDatabase
in the following manner. However, I recently came across an acticle explaining the Functional Decomposition AntiPattern in the Object Oriented paradigm. This article mentioned tell-tale signs of this AntiPattern are classes with the names of a function and classes with only one or two methods. My framework exhibits both of these tell-tale signs.
I find the code works quite well as it provides a blueprint for creating new queries, removes the chances of forgetting to close a connection or cursor and enables easy reuse of boilerplate code. So if this is in fact an anti-pattern, I would have to reconsider the way I design software, as I use the same construct for communicating with my webserver (a framework built around AbstractHttpRequest, AbstractGet and AbstractPost classes).
The following example shows how this framework would go about blueprinting get
and update
requests to the database. Please keep in mind that these code snippets are heavily simplified for the sake of easy understanding.
A database query would then be executed by calling, for example:
UserEntity user = new GetUserById(12).submit();
My question is this: is the following code an example of the Functional Decomposition AntiPattern, the Strategy Pattern or something else entirely? If it is something else entirely, would you consider this good code or bad?
Class Diagram
AbstractDatabaseQuery
public class AbstractDatabaseQuery<T>
{
private DatabaseMode mDatabaseMode;
public AbstractDatabaseQuery(DatabaseMode mode)
{ this.mDatabaseMode = mode;}
public abstract T perform(SQLiteDatabase database);
public T submit()
{
//Get readable or writeable database depending on the mode.
SQLiteDatabase database = this.mDatabaseMode.getDatabase();
//Perform the query.
T result = perform(database);
//Close the connection.
this.mDatabaseMode.close(database);
//Return the result.
return result;
}
}
AbstractGet
public class AbstractGet<T extends AbstractDatabaseEntity> extends AbstractDatabaseQuery<T>
{
public abstract String getQuery( )
public abstract String[ ] getQueryParameters( )
public abstract T getResultFrom(Cusor cursor)
public AbstractGet( )
{ super(DatabaseMode.Read);}
@Override
public T perform(SQLiteDatabase database)
{
//Perform the query.
Cursor cursor = database.rawQuery(getQuery( ), getQueryParameters( ));
//Defer getting the query result to the concrete implementation of the class.
T result = getResultFrom(cursor);
//Close the cursor.
cursor.close();
//Return the result.
return result;
}
}
AbstractUpdate
public class AbstractUpdate<T extends AbstractDatabaseEntity> extends AbstractDatabaseQuery<Integer>
{
public abstract AbstractDatabaseTable getTable();
public abstract ContentValues insertUpdates(ContentValues cv, AbstractDatabaseTable table);
public abstract String getWhereClause( );
public abstract String getWhereArguments( );
private T mEntity;
public AbstractUpdate(T entity)
{
super(DatabaseMode.Write);
this.mEntity = entity;
}
@Override
public Integer perform(SQLiteDatabase database)
{
//Get the table object, which contains (amongst others) the table name and its columns.
AbstractDatabaseTable table = getTable();
//Create the object holding the new values.
ContentValues cv = new ContentValues();
//Add default values such as the last time-of-editing.
if(table.hasEditedColumn())
{ cv.put(AbstractDatabaseTable.KEY_EDITED, System.currentTimeMillis());}
//Defer adding the rest of the new values to the concrete implementation of the class.
cv = insertUpdates(cv, table);
//Safe-guard for the concrete implementation of the class returning null instead of cv.
if(cv == null)
{ throw new IllegalStateException("ContentValues cv is null. Please make sure you return cv from insertUpdates(cv, table)");}
//Perform the operation and return the amount of rows affected.
int rowsAffected = database.update(table.getName( ), cv, getWhereClause( ), getWhereArguments( ));
return rowsAffected;
}
}
GetUserById
public class GetUserById extends AbstractGet<UserEntity>
{
private long mUserId;
public GetUserById(long userId)
{
super();
this.mUserId = userId;
}
@Override
public String getQuery( )
{
DatabaseManager dm = DatabaseManager.getInstance();
AbstractDatabaseTable users = dm.getTableById(R.id.database_table_users);
return "SELECT * FROM " + users + " WHERE _id = ?";
}
@Override
public String[ ] getQueryParameters( )
{ return new String[ ] {String.valueOf(this.mUserId)};}
@Override
public UserEntity getResultFrom(Cursor cursor)
{ return new UserEntity(cursor);}
}
GetAllUsers
public class GetAllUsers extends AbstractGet<ArrayList<UserEntity>>
{
@Override
public String getQuery()
{ return "SELECT * FROM User";}
@Override
public String[ ] getQueryParameters( )
{ return null;}
@Override
public UserEntity getResultFrom(Cursor cursor)
{
ArrayList<UserEntity> users = new ArrayList();
for(cursor.moveToFirst() ; ! cursor.isAfterLast() ; cursor.moveToNext())
{ users.add(new UserEntity(cursor));}
return users;
}
}
UpdateUser
public class UpdateUser extends AbstractUpdate<UserEntity>
{
public UpdateUser(UserEntity userEntity)
{ super(userEntity);}
@Override
public AbstractDatabaseTable getTable()
{ return DatabaseManager.getInstance().getTableById(R.id.database_table_users);}
@Override
public ContentValues insertUpdates(ContentValues cv, AbstractDatabaseTable table)
{
AbstractDatabaseColumn columnFirstName = table.getColumnById(R.database_column_user_first_name);
cv.put(columnFirstName.getName(), this.mEntity.getFirstName());
AbstractDatabaseColumn columnLastName = table.getColumnById(R.database_column_user_last_name);
output(columnLastName.getName(), this.mEntity.getLastName());
return cv;
}
@Override
public String getWhereClause()
{ return AbstractDatabaseTable.KEY_ID + " = ?";}
@Override
public String[] getWhereArguments()
{ return new String[ ] {String.valueOf(this.mEntity.getDatabaseId())};}
}
Aucun commentaire:
Enregistrer un commentaire