Beatles1692, извинете ме, но ще започна с адресиране на основната ви грижа за преработване на обработката на изключения, вместо да прескачам веднага към DSL частта.
Във въпроса си казвате следното:
Преглеждам част от кодова база и стигам до частта за обработка на изключения, която е наистина объркана. Бих искал да го заменя с нещо по-елегантно.
което предполагам е основната ви грижа. Така че ще се опитам да ви дам ръководство за това как да го направите по-елегантен - всъщност кодовият фрагмент, който сте предоставили, не е елегантен, не е за това, че не е DSL или плавни интерфейси, а за дизайнерските качества. Ако имате излишък и свързване във вашия дизайн, тогава създаването на плавен интерфейс върху това излишък и свързване само ще го направи "по-красива бъркотия".
Отговорът ще бъде дълъг и ще се позова на някои качества на кода, така че ако имате нужда от допълнителни обяснения, просто ме уведомете. Тъй като има много променливи, включени във вземането на такова решение (като цена на промяната, собственост върху кода и т.н.), ще се опитам да ви осигуря „най-чистото“ решение и след това с такова, което изисква най-малко усилия.
В ситуации като тези е добре да приложите съветите на Gang of Four, авторите на класическите Design Patterns. Този съвет е: „Инкапсулирайте това, което варира“. във вашия случай обработката на грешки е това, което варира и вариацията се основава на типа изключение. Как да го приложа тук?
Първо решение - преработете напълно миризмите от кода
Първият въпрос, който бих задал, е следният: свободен ли сте да променяте кода, който хвърля изключенията? Ако е така, бих се опитал да капсулирам не вътре в кода, където улавяте изключенията, но вътре в кода, който ги хвърля. Това може да изглежда странно за вас, но това може да ви позволи да избегнете излишък. Както е, вашият код в момента е свързан с тип изключение на две места.
Първото място е мястото, където го хвърляте (трябва да знаете кое изключение да хвърлите) - силно опростено, може да изглежда така:
if(someSituationTakesPlace())
{
throw new ExpType1();
}
else if(someOtherSituationTakesPlace()
{
throw new ExpType2();
}
и т.н. Разбира се, условията може да са по-сложни, може да има множество различни обекти и методи, където хвърляте, но по същество винаги се свежда до поредица от избори като „В ситуация A, хвърлете изключение X“.
Второто място, където имате това картографиране, е когато хванете изключението - трябва отново да преминете през поредица от if-elses, за да разберете каква е била ситуацията и след това да извикате някаква логика, която да се справи с нея.
За да избегна това излишество, бих избрал обработката там, където хвърляте изключението - трябва да имате цялата информация, от която се нуждаете, там. Затова първо бих дефинирал клас изключение като този:
public class Failure : Exception
{
IFailureHandling _handling;
public Failure(IFailureHandling handling)
{
//we're injecting how the failure should be handled
_handling = handling;
}
//If you need to provide additional info from
//the place where you catch, you can use parameter list of this method
public void Handle()
{
_handling.Perform();
}
}
След това бих направил фабрика, която създава такива изключения, обвързвайки ги, вече в този момент, с манипулатори. напр.:
public class FailureFactory
{
IFailureHandling _handlingOfCaseWhenSensorsAreDown,
IFailureHandling _handlingOfCaseWhenNetworkConnectionFailed
public FailureFactory(
IFailureHandling handlingOfCaseWhenSensorsAreDown,
IFailureHandling handlingOfCaseWhenNetworkConnectionFailed
//etc.
)
{
_handlingOfCaseWhenSensorsAreDown
= handlingOfCaseWhenSensorsAreDown;
_handlingOfCaseWhenNetworkConnectionFailed
= handlingOfCaseWhenNetworkConnectionFailed;
//etc.
}
public Failure CreateForCaseWhenSensorsAreDamaged()
{
return new Failure(_handlingOfCaseWhenSensorsAreDown);
}
public Failure CreateForCaseWhenNetworkConnectionFailed()
{
return new Failure(_handlingOfCaseWhenNetworkConnectionFailed);
}
}
Обикновено създавате само една такава фабрика за цяла система и го правите на мястото, където инстанцирате всички дълго работещи обекти (обикновено има едно такова място в приложението), така че, докато инстанцирате фабриката, трябва да можете да предавате всички обектите, които искате да използва чрез конструктора (колкото и смешно да е, това ще създаде много примитивен плавен интерфейс. Не забравяйте, че плавните интерфейси са свързани с четимост и поток, а не само с поставяне.a.dot.every.method.call :-) :
var inCaseOfSensorDamagedLogItToDatabaseAndNotifyUser
= InCaseOfSensorDamagedLogItToDatabaseAndNotifyUser(
logger, translation, notificationChannel);
var inCaseOfNetworkDownCloseTheApplicationAndDumpMemory
= new InCaseOfNetworkDownCloseTheApplicationAndDumpMemory(
memoryDumpMechanism);
var failureFactory = new FailureFactory(
inCaseOfSensorDamagedLogItToDatabaseAndNotifyUser,
inCaseOfNetworkDownCloseTheApplicationAndDumpMemory
);
По този начин както мястото, където хвърляте изключението, така и мястото, където го улавяте, са отделени от логиката на обработка - което е, което варира във вашия проблем! По този начин сме капсулирали това, което варира! Разбира се, вие сте свободни да предоставите по-усъвършенстван плавен интерфейс в допълнение към това.
Сега всяко място, където хвърляте изключение, ще изглежда така:
if(sensorsFailed())
{
throw _failureFactory.CreateForCaseWhenSensorsAreDamaged();
}
И място, където улавяте всички тези изключения, ще изглежда така:
try
{
PerformSomeLogic();
}
catch(Failure failure)
{
failure.Handle();
}
По този начин единственото място, където ще знаете как да се справите с всеки случай на грешка, ще бъде в логиката, която създава обект от клас FailureFactory.
Второ решение - използвайте манипулатор
В случай, че не притежавате кода, който хвърля изключенията, или би било твърде скъпо или твърде рисковано да се постави в описаното по-горе решение, бих използвал обект Handler, който би изглеждал подобно на FailureFactory, но вместо да създава обекти , той сам би изпълнил обработката:
public class FailureHandlingMechanism
{
_handlers = Dictionary<Type, IFailureHandling>();
public FailureHandler(Dictionary<Type, IFailureHandling> handlers)
{
_handlers = handlers;
}
public void Handle(Exception e)
{
//might add some checking whether key is in dictionary
_handlers[e.GetType()].Perform();
}
}
Създаването на такъв механизъм за обработка вече ще ви даде много примитивен плавен интерфейс:
var handlingMechanism = new HandlingMechanism(
new Dictionary<Type, IFailureHandling>()
{
{ typeof(NullPointerException), new LogErrorAndCloseApplication()}},
{ typeof(ArgumentException}, new LogErrorAndNotifyUser() }
};
Ако желаете още по-плавен и по-малко шумен начин за конфигуриране на такъв механизъм за обработка, можете да създадете конструктор около HandlingMechanism, който има методи за добавяне на ключове и стойности към речника и метод, наречен Build(), който създаде обекта за вас:
var handlingMechanismThatPerforms = new HandlingMechanismBuilder();
var logErrorAndCloseApplication = new LogErrorAndCloseApplication();
var logErrorAndNotifyUser = new LogErrorAndNotifyUser();
var handlingMechanism = handlingMechanismThatPerforms
.When<NullPointerException>(logErrorAndCloseApplication)
.When<ArgumentException>(logErrorAndNotifyUser)
.Build();
И това е. Кажете ми, ако ви помага по някакъв начин!
person
Grzesiek Galezowski
schedule
27.01.2013