Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# pattern for avoiding a leaky abstraction when one of the implementation requires an extra step

I am implementing an ITracker interface that looks something like:

public interface ITracker
{
    void Track(ITrackerEvent trackerEvent);
}

I initially created an implementation of this interface wrapping Mixpanel.NET. I then created another one that wraps Application Insights. However, the Application Insights one requires Flush() to send the data to the server.

I don't want to pollute the ITracker interface with a Flush() method just because one of the implementations needs one. It would feel like a leaky abstraction.

However, I need to call this method at some point (probably on application shutdown) and don't want to do so every time Track is called.

Is is possible to call a method when the Tracker is garbage collected at the end of the session? Is this even a good approach?

I feel like I'm missing a trick here!

like image 312
James Bateson Avatar asked Mar 09 '16 13:03

James Bateson


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.


2 Answers

I'd make ITracker to use transaction alike pattern because there may be cases when you might want to discard tracker events, rollback or modify them:

public interface ITracker
{
    void Track(ITrackerEvent trackerEvent);

    void Commit();
}

And then per implementations:

  • I'd call Flush inside Commit for Application insights.
  • I'd write tracker events in in-memory collection (List<ITrackerEvent> or BlockingCollection<ITrackerEvent> if concurrency involved) inside Track method and then use your current logic to call Mixpanel.NET api inside Commit method implementation for Mixpanel.NET.

Recommendation: ITracker should also implement IDisposable since trackers usually use resources that need to be disposed.

like image 137
Leri Avatar answered Oct 04 '22 21:10

Leri


Building on Leri, I would think more along the lines of what a tracker might need to do.

I would be inclined to do something like this:

public interface ITracker {
    void BeginTracking();
    void Track(ITrackerEvent trackerEvent);
    void EndTracking();
}

Then all trackers have a sense of when they're starting and when they're finishing. This is important because a tracker may be holding onto resources that shouldn't be held longer than necessary. If a tracker doesn't need to use either BeginTracking or EndTracking, the implementation is trivial. A trivial implementation is not a leaky abstraction. A leaky abstraction is one that doesn't work for all implementations.

Now suppose you are flat-out against having two extra methods in each tracker (why?). You could instead have ITrackerEvents that are out of band and cover the semantic meaning of Begin and End. I don't like this. It requires every tracker to have special code to handle out of band events.

You can also have a separate interface

public interface IDemarcatedTracker : ITracker {
    void BeginTracking();
    void EndTracking();
}

which requires you to have special case code in your calling code to check to see if the ITracker is also an IDemarcatedTracker:

public void BeginTracking(ITracker tracker)
{
    IDemarcatedTracker demarcatedTracker = tracker as IDemarcatedTracker;
    if (demarcatedTracker != null)
        demarcatedTracker.BeginTracking();
}

And not to over blow things too much, but I would also wonder what is supposed to happen when a tracker fails? Just blindly throw an exception? And here is where the abstraction is actually leaky. There is no process for a tracker to let you know that it can't track.

In your case you might want to return a boolean (limited information), an error code (somewhat more information), or an error class/struct. Or you might want to have a standard exception that gets thrown. Or you might want the Begin() method to include a delegate to call when an error happens in tracking.

like image 32
plinth Avatar answered Oct 04 '22 20:10

plinth