Friday, August 31, 2007

Repeated operations shortcut

   Когато пиша някакъв код се стремя той да е макасимално четим(разбирай опростен и подреден), но с времето нещата набъбват и четимоста отстъпва място на елегантноста. Ето и конкретния случай. Имам си класа SFAdapter, който прави връзката към SalesForce.com и изпънява заявките. Този менаджер може да бъде всъшност всякакъв примерно към базата. В него имах само три неща - инициализация на връзката, логин и метод за подаване на заявките:

................
public SFAdapter(final String sfUser, final String sfPass) {
if( sfUser==null || sfPass==null ) return;

this.sfUser=sfUser;
this.sfPass=sfPass;
try {
binding = (SoapBindingStub) new SforceServiceLocator().getSoap(new URL("some_url"));
} catch (Exception e) {

.....................

protected void doLogin() .............

public QueryResult query(final String ) .............

Да обаче проеката си нараства и се налага да се добавят все повече и повече методи, който са абсолуютно еднакви като структура:
public типа_които_ще_върне име(параметри) {
try {

изпънява_заявката

// следват нещата, които ще направи при грешка
} catch (UnexpectedErrorFault uef) {


И какво се получава - много писане. Верно проситчки неща ама много писане беее. Сегиз-тогиз е добре да се консултираш. Ето в какво се превърна кода след консултацията с колегите.
   Повтарящите се неща да се на едно място. Операциите трябва да се уеднаквят. Това е посоката на мислене. И самия код:
public void delete(final String[] ids) {
executeInSf(new WriteCommand<Object>() {
public Object execute() throws UnexpectedErrorFault, RemoteException {
binding.delete(ids);
return null;
}
});
}

public SObject[] retrieve(final String fieldList, final String sObjectType, final String[] ids) {
return executeInSf(new ReadCommand<SObject[]>() {
public SObject[] execute() throws UnexpectedErrorFault, RemoteException {
return binding.retrieve(fieldList, sObjectType, ids);
}
});
}

private <T> T executeInSf(Command<T> c) throws RecoverableSFException, CriticalSFException {
try {
return c.execute();
} catch (UnexpectedErrorFault uef) {
if (uef.getExceptionCode().equals(ExceptionCode.INVALID_SESSION_ID)) {
try {
doLogin();
return c.execute();
} catch (UnexpectedErrorFault e) {
throw c.createException(e);
} catch (InvalidIdFault e) {
................... 
}

interface Command<T extends Object> {
T execute() throws UnexpectedErrorFault, RemoteException;
RecoverableSFException createException(Exception cause);
}

abstract class ReadCommand<T extends Object> implements Command<T>{
public SFReadException createException(Exception cause) {
return new SFReadException(cause);
}
}

abstract class WriteCommand<T extends Object> implements Command<T>{
public SFWriteException createException(Exception cause) {
return new SFWriteException(cause);
}
}

С простички думи. Операцията, която трябва да се изпълни я дефинираме в интерфейс(Command), а нещата които се повтарят в един метод, който приема като параметър този инерфейс(executeInSf). Следва и самото изпълнение(retrieve), която не прави нищо друго освен да иплементира изпълнението на операцията - execute метода.
NOTE: В метода delete забележете, че нама return, а execute трябва задължително да върне стойност и тя е null.
Малко се усложниха нещата, защото имам два вида грешки:
try {
doLogin();
return c.execute();
} catch (UnexpectedErrorFault e) {
// see here!
throw c.createException(e);
...........

Правилото е, ако има има различия и допълнителни неща те се отделят в абстрактни класове, които наследяват инерфейса.


И всичко това само, защото в Java не можем да предаваме функции. Ако беше така, на executeInSf щахме да предадем функцията, която трябва да се изпълни. Може би посоката на мислене трябва да е към езици за функционално програмиране.

2 comments:

Anonymous said...

Добре де, толкова е просто. Разбере ли човек как да присвои анонимна функция конците се разплитат.

В Java се използва callback:

interface IFromIAndI {
Integer call(Integer a, Integer b);
}

IFromIAndI add_two_integers = new IFromIAndI() {
public Integer call(final Integer a, final Integer b) {
return a + b;
}
};


Това е равносилно на:

add_two_integers = lambda...


След като сме ги дефинирали по този начин нещата използването е много лесно:

add_two_integers.call(35, 42);


т.е. извикваме "анонимната" функция.
Ама все от много далече почваш нещата или просто обичаш да пишеш много :)

Anonymous said...

Здрасти,
Това е тъй наречения "Execute around" идиом. Виж слединя код:

public interface InputStreamAction
{
void useStream(InputStream stream) throws IOException;
}

// Somewhere else

public void executeWithFile(String filename, InputStreamAction action)
throws IOException
{
InputStream stream = new FileInputStream(filename);
try {
action.useStream(stream);
} finally {
stream.close();
}
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
public void useStream(InputStream stream) throws IOException
{
// Code to use the stream goes here
// The calling code doesn't need to worry about the open/clean-up side.
// It will be taken care of by "executeWithFile"
}
});



Хвърли и един поглед на Template Method Pattern ;)

c++ (3) daily (4) emacs (2) freebsd (4) java (3) javascript (1) linux (2) Lisp (3) misc (8) programming (13) source control (4) sql (1) думи (8)