Когато пиша някакъв код се стремя той да е макасимално четим(разбирай опростен и подреден), но с времето нещата набъбват и четимоста отстъпва място на елегантноста. Ето и конкретния случай. Имам си класа 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:
Добре де, толкова е просто. Разбере ли човек как да присвои анонимна функция конците се разплитат.
В 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);
т.е. извикваме "анонимната" функция.
Ама все от много далече почваш нещата или просто обичаш да пишеш много :)
Здрасти,
Това е тъй наречения "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 ;)
Post a Comment