jeudi 29 septembre 2016

Is this an example of the Functional Decomposition AntiPattern, Strategy pattern or something else entirely?

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

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