简体   繁体   中英

Merging these two very similar methods

I have these two methods on a service:

public Offer GetOffer(int id, string languageCode = Website.LanguageSettings.DefaultLanguageCode)
        {
            Entities.Offer offerEntity = _db.Offers.FirstOrDefault(offer => offer.Id == id);

            if (languageCode.ToLower(CultureInfo.InvariantCulture) != Website.LanguageSettings.DefaultLanguageCode)
            {
                using (IDocumentSession session = store.OpenSession())
                {
                    Translation.Offer translatedOffer = session.LuceneQuery<Translation.Offer>(Website.RavenDbSettings.Indexes.Offers)
                        .Where(string.Format("ObjectId:{0} AND LanguageCode:{1}", id, languageCode))
                        .OrderByDescending(offer => offer.Id)
                        .FirstOrDefault();

                    var offerPOCO = Mapper.DynamicMap<Translation.Offer, Offer>(translatedOffer);
                    offerPOCO.Id = offerEntity.Id;

                    return offerPOCO;
                }
            }

            return Mapper.Map<Entities.Offer, Offer>(offerEntity);
        }

And

public Hotel GetHotel(int id, string languageCode = Website.LanguageSettings.DefaultLanguageCode)
        {
            Entities.Hotel hotelEntity = _db.Hotels.FirstOrDefault(hotel => hotel.Id == id);

            if (languageCode.ToLower(CultureInfo.InvariantCulture) != Website.LanguageSettings.DefaultLanguageCode)
            {
                using(IDocumentSession session = store.OpenSession())
                {
                    Translation.Hotel translatedHotel = session.LuceneQuery<Translation.Hotel>(Website.RavenDbSettings.Indexes.Hotels)
                        .Where(string.Format("ObjectId:{0} AND LanguageCode:{1}", id, languageCode))
                        .OrderByDescending(hotel => hotel.Id)
                        .FirstOrDefault();

                    Hotel hotelPOCO = Mapper.DynamicMap<Translation.Hotel, Hotel>(translatedHotel);
                    hotelPOCO.Id = hotelEntity.Id;

                    return hotelPOCO;
                }
            }

            return Mapper.Map<Entities.Hotel, Hotel>(hotelEntity);
        }

They are exactly the same in most aspects: they take the same params, build the same query and make the same operations, the only thing that varies is the type of objects they work with and output. Besides building a method to generate the Where() param string, I can't think of any way I can merge most (or all) of this code into a single method and then just call it from the GetOffer() and GetHotel() methods since I'll end up with several more just like these two.

Any advice is greatly appreciated.

Edit: adding the solution so if another poor soul comes across this problem he/she can have a starting point:

private TReturn GetObject<TReturn, TEntity, TTranslation>(int id, string languageCode, string ravenDbIndex) where TEntity:EntityObject 
                                                                                                                    where TTranslation:Translation.BaseTranslationObject
                                                                                                                    where TReturn:BasePOCO
        {
            // TODO Run more tests through the profiler
            var entities = _db.CreateObjectSet<TEntity>();
            var entityKey = new EntityKey(_db.DefaultContainerName + "." + entities.EntitySet.Name, "Id", id); // Sticking to the Id convention for the primary key
            TEntity entity = (TEntity)_db.GetObjectByKey(entityKey);

            if(languageCode.ToLower(CultureInfo.InvariantCulture) != Website.LanguageSettings.DefaultLanguageCode)
            {
                using(IDocumentSession session = store.OpenSession())
                {
                    TTranslation translatedObject = session.LuceneQuery<TTranslation>(ravenDbIndex)
                        .Where(string.Format("ObjectId:{0} AND LanguageCode:{1}", id, languageCode))
                        .OrderByDescending(translation => translation.Id)
                        .FirstOrDefault();

                    TReturn poco = Mapper.DynamicMap<TTranslation, TReturn>(translatedObject);
                    poco.Id = id;

                    return poco;
                }
            }

            return Mapper.Map<TEntity, TReturn>(entity);
        }

And then I just call:

GetObject<Hotel, Entities.Hotel, Translation.Hotel>(id, languageCode, Website.RavenDbSettings.Indexes.Hotels);

Whenever I need a hotel.

Thank you all for the great replies, learned quite a lot from them.

It looks as though you could refactor this into a generic method. Something similar to this (I'm making some assumptions about the ability to refactor some method calls, etc. But hopefully you get the idea)

public T Get<T>(int id, string languageCode = Website.LanguageSettings.DefaultLanguageCode)
        {
            Entity<T> entity = _db<T>.FirstOrDefault(entity => entity.Id == id);

            if (languageCode.ToLower(CultureInfo.InvariantCulture) != Website.LanguageSettings.DefaultLanguageCode)
            {
                using(IDocumentSession session = store.OpenSession())
                {
                    Translation<T> translatedEntity = session.LuceneQuery<Translation<T>>(Website.RavenDbSettings.Indexes.Entities<T>)
                        .Where(string.Format("ObjectId:{0} AND LanguageCode:{1}", id, languageCode))
                        .OrderByDescending(entity=> entity.Id)
                        .FirstOrDefault();

                    T POCO = Mapper.DynamicMap<Translation<T>, T>(translatedEntity);
                    POCO.Id = entity.Id;

                    return POCO;
                }
            }

            return Mapper.Map<Entities<T>, T>(Entity);
        }

Suggest keeping them as-is. They return different types, and warrant different methods. My gut feeling is that it falls under Do One Thing - Single Responsibility Principle .

True that they implement the same strategies in doing their work, but I'd suggest that if you were to merge/refactor them into one would be more confusing than benefiting.

Consider the likelihood of the business logic changing. Would one be more volatile than the other? Would implementing this be same as GetFlights() and GetCarRentals() ?

I realize that when making code the same, and likely copy/pasting/tweaking code between methods, you get the sense that you could reduce lines of code, and don't repeat yourself. I value both SRP and DRY equally, but in this case, I'd rather read and maintain different methods for each entity of Offer , Hotel , etc.

In cases like this I isolate the variable terms and put them in the sig, if the sig turns out too large and difficult/ugly to call then see if generics can make this simpler or if parts can be factored into their types which is what I would think to do in this case. Also having an action or a func in the sig can help if it would never be difficult to fill in at call, something where you could maybe call:

GetDalObject(db => db.Hotels.FirstOrDefault(hotel => hotel.Id == id), ...

Then you can swap it in the call between hotel or offer or etc, but in your case I don't know how much this can help since I think the sig would really become nasty, so I would look at taking the variable parts and implementing them in the Offer type and the Hotel type, which you access via an interface, and then the Offer/Hotel class is handed to this method.

public interface ICommonDalObject
{
    public string LuceneQueryString { get; }
    public ITranslation GetTranslation();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM