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 ;)

algorithms (1) cpp (3) cv (1) daily (4) emacs (2) freebsd (4) java (3) javascript (1) JSON (1) linux (2) Lisp (7) misc (8) programming (16) Python (4) SICP (1) source control (4) sql (1) думи (8)