Libzypp/Design/Resolvable/BinaryCompatibility
İkili bağdaşırlık
Şahsen ben bunu dert etmiyorum. Ne yazık ki diğerleri ediyor. Uzun süreden beri ona bakmak zorunda olduğumuz için, gelecekte değişiklikler olacak. Onları yapmak için ne kadar zaman harcadığımız önemli değil şu an,interface(arayüz)'de yapılması gereken değişikler için işe yarayacak bir şey olacak.Resolvables için bu genellikle yeni özellikler olarak tanıtılıyor.
Yeni bir özellik ya Resovable 'da yeni bir veri öğesi ya da yukarıdaki interface sınıflarından birinde olan yeni bir sanal yöntem. İkisi de binary compatibility(ikili bağdaşırlık)'i bozuyor.
Bunu önlemeye çalışabiliriz (her ne kadar, gelecek kontrol edemeyeceğimiz diğer özellikleri bulacak olsada).Bunun bedelini ödemek zorunda kalacağımız açık.
- interface class(arayüz sınıfı)'da hiç yeni veri üyesi yok
İşte temelde bu gerekçe yüzünden zypp::Resolvable böyle görünüyor :
class Resolvable { public: // Sanal ve sıklıkla kullanılan veriler içerde saklanıyor ve // Resolvable tarafından sağlanıyor. dizgi adı () const; dizgi düzenlemesi() const; ... özel: struct Impl; base::ImplPtr<Impl> _pimpl; };
tüm veri üyeleri özel bir struct Impl 'un içine gidiyor. Veri üyelerine ulaşmak artık içe katılamayacak ancak Resolvable.cc da yapılmak zorunda olucak. veri üyeleri artık içe katılamayacak, fakat içeride yapılmak zorunda olucak. İşte bu Impl ın yerini belirlemenin ve serbest bırakmanın ikisinin birden bedeli. .
struct Impl 'ı değiştirmek binary compatibility (ikili bağdaşırlığı) zorlamıyor, çünkü o görünebilir bir göstergeç. Yeni veri üyelerine ulaşmak için yeni bir sanal olmayan üye eklemede bir sorun yok.
- hiç yeni sanal işlem yok.
Bu biraz pahalı ve zahmetli. Hatırla ResObject:
class ResObject : public Resolvable { public: sanal dizgi özeti() const { return ""; } sanal dizgi tanımı() const { return ""; } ... };
Sanallığı bazı gizli sınıflara koyarak saklamak zorundayız. Resolvable verilerinin saklanma yöntemiyle benzer bir biçimde. Arayüz sınıf saklama uygulamaları (class hides the implementation interface)olarak adlandırdığım :
class ResObjectImplIf; // saklı arayüz uygulamaları class ResObject : public Resolvable { public: typedef ResObjectImplIf Impl; public: dizgi özeti() const; dizgi tanımı() const; private: /** Uygulamaya Giriş */ virtual Impl & pimpl() = 0; virtual const Impl & pimpl() const = 0; }
// paket için de aynı uygulama geçerli
class PackageImplIf; // hide implementation interface class Package : public ResObject { public: typedef PackageImplIf Impl; ... private: /** Access implementation */ virtual Impl & pimpl() = 0; virtual const Impl & pimpl() const = 0; }
Şimdi binanın önyüzünü inşa ettik.Bir package uygulanımı için resmi sıra düzeni şöyleydi:
ResObject->Package->MyPackageImplementation
Şimdi ise;
ResObjectImplIf->PackageImplIf->MyPackageImplementation
The visible interface to access the object is still
ResObject->Package
What's left, is to connect facade and implementation. After creating a
new MyPackageImplementation;
We could construct the facade, and store the MyPackageImplementation* (which is a PackageImplIf* too) in class package, and pass it down to store it as well in ResObject (it's a ResObjectImplIf* as well). That way we have one pointer on each level, all pointing in fact to the same implementation object. Additionally all kind of sanity checks and maintenance work has to be realized on each level.
What checks? Which maintenance?
- E.g. not passing a NULL implementation, or taking care that there
is a default implementation available and used, if a NULL is passed. (incl. error reporting, exception handling)
- You may have noticed that the implementation hierarchy starts with
ResObjectImplIf. So where is the Resolvable? For several reasons it's in the interface, and has a private implementation class, which is not to be exposed or available for derivation.
Impact: We need a backlink from the implementation to the interface, to let the implementation access the objects resolvable data. A real Resolvable* (not ::Ptr) to avoid a reference cycle, otherwise the whole Object will never vanish.
Bonus: The backlink indicates whether the implementation part is already connected to an interface part. You can't accidentally connect the same implementation object to different Resolvables.
All this has to be realized on each level! Because each interface class could be the topmost one. Creating a ResObject, ResObject is responsible; creating a Package, Package has to check; and once we derive from Package The new classes will have to check.
That's a maintenance issue. If checks have to be changed for some reason, they have to be changed in all classes. We should not forget one, and we can't cover classes which are not part of zypp (even if we don't care about them now). If we introduce new classes, we must not forget to implement the checks there.
That's why I decided not to do it this way. And again, we have to pay for it ;-)
Instead of storing the pointer to implementation on each level, and having the checks on each level, each level has a pure virtual method pimpl() (private implementation), which provides access to the implementation via the topmost class in the current hierarchy.
It returns a Impl &, so the topmost class is in charge not to provide NULL implementations, and the interface classes can rely on this.
(still here, or time for a break ;-)
Obviously none of the current interface classes can be the 'topmost'. This would prevent us from extending the hierarch in the future. And Package is just a part of the complete Resolvable tree (Selection, Patches...).
As we need a class which fits on top, wherever the top is, it's template class. You won't find it, and if you do, you don't want to use it ;) (It's zypp::detail::_resobjectfactory_detail::ResImplConnect<class _Res>)
All it does is connecting an interface class derived from Resolvable to an implementation derived from ResObjectImplIf. This is the class which implements pimpl().
base::ReferenceCounted ^ Resolvable <---------+ ^ backlink ResObject +-----------ResObjectImplIf ^ ^ Package PackageImplIf ^ ^ ResImplConnect<Package> --------> MyPackageImplementation
As I said, de-virtualizing the interface is somewhat expensive. Expensive in terms of writing lines of code. Introduce a new Package attribute:
Package.h string newattr() const;
Package.cc string Package::newattr() const { return pimpl().newattr(); }
PackageImplIf.h virtual string newattr() const =0; or virtual string newattr() const { return ""; /*or whatever is appropriate*/ }
MyPackageImplementation.h Must implement (in case of =0;), or may overload to provide the real value.
Runtime penalty is not as much as one may assume. The ImplIf hierarchy is what we'd need anyway. The base::ReferenceCounted would be needed too.
Memory: The visible interface (Resolvable -> Package) contains no data, ResImplConnect<Package> just a smart pointer to MyPackageImplementation. Plus 2 entries in the vtable per lever for pimple(). But the vtabe is global, not per object. Thus a pointer per object.
Runtime: One function call because we can't inline, and one virtual call for pimpl(). Not that much either.
Last edit in Trac '11/24/05 18:32:39' by 'kkaempf'
Last edit in Trac '11/24/05 18:32:39' by 'kkaempf'
Last edit in Trac '11/24/05 18:32:39' by 'kkaempf'
Last edit in Trac '11/24/05 18:32:39' by 'kkaempf'