【问题标题】:Passing interface as a parameter to the Class constructor将接口作为参数传递给类构造函数
【发布时间】:2019-09-12 08:16:45
【问题描述】:

我正在使用 iText7 读取 PDF 文件数据并根据它们在 PDF 文件中的位置创建一个 xml 文件。 我的问题是,

iText7 程序集中有一个类LocationTextExtractionStrategy,如下图所示

public class LocationTextExtractionStrategy : ITextExtractionStrategy, IEventListener
{
    public LocationTextExtractionStrategy(ITextChunkLocationStrategy strat);
    public virtual void EventOccurred(IEventData data, EventType type);

    public interface ITextChunkLocationStrategy
    {
        ITextChunkLocation CreateLocation(TextRenderInfo renderInfo, LineSegment baseline);
    }
}

现在我将这个类继承到我的项目类中,如下所示

public class TextLocationExtractionStrategy : LocationTextExtractionStrategy
{
    ITextChunkLocationStrategy locationstrategy ;
    public TextLocationExtractionStrategy(ITextChunkLocationStrategy strategy)
    {
        locationstrategy = strategy;
    }

    public override void EventOccurred(IEventData data, EventType type)
    {
        if (!type.Equals(EventType.RENDER_TEXT))
            return;

        TextRenderInfo renderInfo = (TextRenderInfo)data;

        string curFont = renderInfo.GetFont().GetFontProgram().ToString();

        float curFontSize = renderInfo.GetFontSize();

        IList<TextRenderInfo> text = renderInfo.GetCharacterRenderInfos();
        foreach (TextRenderInfo t in text)
        {
            LineSegment lineSegment = t.GetBaseline();
            string letter = t.GetText();
            Vector letterStart = t.GetBaseline().GetStartPoint();
            Vector letterEnd = t.GetAscentLine().GetEndPoint();
            Rectangle letterRect = new Rectangle(letterStart.Get(0), letterStart.Get(1),
                letterEnd.Get(0) - letterStart.Get(0), letterEnd.Get(1) - letterStart.Get(1));

            if (letter != " " && !letter.Contains(' '))
            {
                ITextChunkLocation loc = locationstrategy.CreateLocation(t, lineSegment);
                UTextChunk chunk = new UTextChunk(t, t.GetText(), loc);
                t.GetText();
                t.GetBaseline().GetBoundingRectangle();

                m_locationResult.Add(chunk);
            }
        }
    }
}

EventOccurred方法中,我调用了基类的CreateLocation方法(在if条件内)。

现在我的问题是,如何将接口作为参数传递给派生类的构造函数,以便我可以轻松调用基类的CreateLocation方法?

我正在尝试如下所示,但我不明白如何创建一个合适的对象或其他可以传递给构造函数的对象,借助它我可以调用 CreateLocation 方法。

LocationTextExtractionStrategy.ITextChunkLocationStrategy locst = null;
TextLocationExtractionStrategy strategy = new TextLocationExtractionStrategy(locst);
PdfTextExtractor.GetTextFromPage(page, strategy))

由于我们无法创建接口的实例,我不知道如何将值传递给接口类型的参数。在上面的几行中,我只是将它分配给 null 并调用构造函数,但是分配给它的 null 值,它会说“对象引用未设置为对象的实例”。

你能帮我解决这个问题吗?

如果在提出我的问题的方式上有任何问题,请告诉我或更正我。

【问题讨论】:

  • 您需要传入一个实现此接口的类的对象实例。
  • 请务必将接口视为对事物发展方式的承诺。不能传递接口,必须传递实现该接口的对象。
  • @LasseVågsætherKarlsen 我虽然将其作为依赖注入,但如果您观察代码,“ITextChunkLocationStrategy”是在基类中创建的接口。如果我的理解有误,请您帮我解决一下代码 sn-p
  • 当您使用 DI 时,您使用接口编写代码,并且在某些时候,您不仅声明“在访问此接口时我们将使用此实现”,而且稍后还会要求您的 DI 引擎检索该实现,无论它是什么被映射到。
  • 我不知道该如何提供帮助。您需要声明一个类(新的或现有的),以便它实现此接口,然后当然要实现该接口。框架中可能已经存在已经执行此操作的类。以我的经验,当一个接口被用来表示一个策略时,已经存在许多你可以选择的实现。可能就像为那个参数传入new SomeExistingStrategyClass() 一样简单。

标签: c# .net itext7


【解决方案1】:

如果您查看基类 LocationTextExtractionStrategy 的源代码(它是开源,所以查看源代码!),您会发现确实如此不仅有带有ITextChunkLocationStrategy 参数的构造函数,它还有一个没有参数的构造函数。

该构造函数实际上实例化了该接口的实现并将其转发给您引用的同一构造函数:

public LocationTextExtractionStrategy()
    : this(new _ITextChunkLocationStrategy_85()) {
}

private sealed class _ITextChunkLocationStrategy_85 : LocationTextExtractionStrategy.ITextChunkLocationStrategy {
    public _ITextChunkLocationStrategy_85() {
    }

    public ITextChunkLocation CreateLocation(TextRenderInfo renderInfo, LineSegment baseline) {
        return new TextChunkLocationDefaultImp(baseline.GetStartPoint(), baseline.GetEndPoint(), renderInfo.GetSingleSpaceWidth());
    }
}

由于您想使用ITextChunkLocationStrategy 实现并且基类没有为其提供getter,因此您不能简单地使用没有参数的其他构造函数。你也不能实例化_ITextChunkLocationStrategy_85 类,因为它是私有的。而且你不能简单地将_ITextChunkLocationStrategy_85 复制到你的代码中,因为TextChunkLocationDefaultImpinternal

不过,您可以将TextChunkLocationDefaultImp 复制到您的代码中,然后将_ITextChunkLocationStrategy_85 复制到您的代码中,将基类中TextChunkLocationDefaultImp 的使用替换为使用该类的副本,以及然后实例化 _ITextChunkLocationStrategy_85 类的副本,最终获得 ITextChunkLocationStrategy 实现实例。

或者,您可以尝试进行反思和内省。不过,这可能会导致维护问题。

如果一个库提供了泛化某些东西的方法,然后隐藏了它的默认实现,那就太麻烦了……


供参考,TextChunkLocationDefaultImp目前实现如下

internal class TextChunkLocationDefaultImp : ITextChunkLocation {
    private const float DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION = 2;

    /// <summary>the starting location of the chunk</summary>
    private readonly Vector startLocation;

    /// <summary>the ending location of the chunk</summary>
    private readonly Vector endLocation;

    /// <summary>unit vector in the orientation of the chunk</summary>
    private readonly Vector orientationVector;

    /// <summary>the orientation as a scalar for quick sorting</summary>
    private readonly int orientationMagnitude;

    /// <summary>perpendicular distance to the orientation unit vector (i.e.</summary>
    /// <remarks>
    /// perpendicular distance to the orientation unit vector (i.e. the Y position in an unrotated coordinate system)
    /// we round to the nearest integer to handle the fuzziness of comparing floats
    /// </remarks>
    private readonly int distPerpendicular;

    /// <summary>distance of the start of the chunk parallel to the orientation unit vector (i.e.</summary>
    /// <remarks>distance of the start of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system)
    ///     </remarks>
    private readonly float distParallelStart;

    /// <summary>distance of the end of the chunk parallel to the orientation unit vector (i.e.</summary>
    /// <remarks>distance of the end of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system)
    ///     </remarks>
    private readonly float distParallelEnd;

    /// <summary>the width of a single space character in the font of the chunk</summary>
    private readonly float charSpaceWidth;

    public TextChunkLocationDefaultImp(Vector startLocation, Vector endLocation, float charSpaceWidth) {
        this.startLocation = startLocation;
        this.endLocation = endLocation;
        this.charSpaceWidth = charSpaceWidth;
        Vector oVector = endLocation.Subtract(startLocation);
        if (oVector.Length() == 0) {
            oVector = new Vector(1, 0, 0);
        }
        orientationVector = oVector.Normalize();
        orientationMagnitude = (int)(Math.Atan2(orientationVector.Get(Vector.I2), orientationVector.Get(Vector.I1)
            ) * 1000);
        // see http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
        // the two vectors we are crossing are in the same plane, so the result will be purely
        // in the z-axis (out of plane) direction, so we just take the I3 component of the result
        Vector origin = new Vector(0, 0, 1);
        distPerpendicular = (int)(startLocation.Subtract(origin)).Cross(orientationVector).Get(Vector.I3);
        distParallelStart = orientationVector.Dot(startLocation);
        distParallelEnd = orientationVector.Dot(endLocation);
    }

    public virtual int OrientationMagnitude() {
        return orientationMagnitude;
    }

    public virtual int DistPerpendicular() {
        return distPerpendicular;
    }

    public virtual float DistParallelStart() {
        return distParallelStart;
    }

    public virtual float DistParallelEnd() {
        return distParallelEnd;
    }

    /// <returns>the start location of the text</returns>
    public virtual Vector GetStartLocation() {
        return startLocation;
    }

    /// <returns>the end location of the text</returns>
    public virtual Vector GetEndLocation() {
        return endLocation;
    }

    /// <returns>the width of a single space character as rendered by this chunk</returns>
    public virtual float GetCharSpaceWidth() {
        return charSpaceWidth;
    }

    /// <param name="as">the location to compare to</param>
    /// <returns>true is this location is on the the same line as the other</returns>
    public virtual bool SameLine(ITextChunkLocation @as) {
        if (OrientationMagnitude() != @as.OrientationMagnitude()) {
            return false;
        }
        float distPerpendicularDiff = DistPerpendicular() - @as.DistPerpendicular();
        if (distPerpendicularDiff == 0) {
            return true;
        }
        LineSegment mySegment = new LineSegment(startLocation, endLocation);
        LineSegment otherSegment = new LineSegment(@as.GetStartLocation(), @as.GetEndLocation());
        return Math.Abs(distPerpendicularDiff) <= DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION && (mySegment.GetLength
            () == 0 || otherSegment.GetLength() == 0);
    }

    /// <summary>
    /// Computes the distance between the end of 'other' and the beginning of this chunk
    /// in the direction of this chunk's orientation vector.
    /// </summary>
    /// <remarks>
    /// Computes the distance between the end of 'other' and the beginning of this chunk
    /// in the direction of this chunk's orientation vector.  Note that it's a bad idea
    /// to call this for chunks that aren't on the same line and orientation, but we don't
    /// explicitly check for that condition for performance reasons.
    /// </remarks>
    /// <param name="other"/>
    /// <returns>the number of spaces between the end of 'other' and the beginning of this chunk</returns>
    public virtual float DistanceFromEndOf(ITextChunkLocation other) {
        return DistParallelStart() - other.DistParallelEnd();
    }

    public virtual bool IsAtWordBoundary(ITextChunkLocation previous) {
        // In case a text chunk is of zero length, this probably means this is a mark character,
        // and we do not actually want to insert a space in such case
        if (startLocation.Equals(endLocation) || previous.GetEndLocation().Equals(previous.GetStartLocation())) {
            return false;
        }
        float dist = DistanceFromEndOf(previous);
        if (dist < 0) {
            dist = previous.DistanceFromEndOf(this);
            //The situation when the chunks intersect. We don't need to add space in this case
            if (dist < 0) {
                return false;
            }
        }
        return dist > GetCharSpaceWidth() / 2.0f;
    }

    internal static bool ContainsMark(ITextChunkLocation baseLocation, ITextChunkLocation markLocation) {
        return baseLocation.GetStartLocation().Get(Vector.I1) <= markLocation.GetStartLocation().Get(Vector.I1) &&
             baseLocation.GetEndLocation().Get(Vector.I1) >= markLocation.GetEndLocation().Get(Vector.I1) && Math.
            Abs(baseLocation.DistPerpendicular() - markLocation.DistPerpendicular()) <= DIACRITICAL_MARKS_ALLOWED_VERTICAL_DEVIATION;
    }
}

【讨论】:

  • 非常感谢@mkl 的详细解释,它通过一些小更新解决了我的问题。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 2019-09-29
  • 2023-04-04
  • 2015-10-11
  • 1970-01-01
相关资源
最近更新 更多